<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: orliesaurus</title>
    <description>The latest articles on DEV Community by orliesaurus (@orliesaurus).</description>
    <link>https://dev.to/orliesaurus</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F88579%2F17f6d1d7-367e-4f37-874a-859114ffd88f.jpeg</url>
      <title>DEV Community: orliesaurus</title>
      <link>https://dev.to/orliesaurus</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/orliesaurus"/>
    <language>en</language>
    <item>
      <title>Tests with Playwright + AI: Superpowers!</title>
      <dc:creator>orliesaurus</dc:creator>
      <pubDate>Thu, 14 Mar 2024 18:19:44 +0000</pubDate>
      <link>https://dev.to/orliesaurus/tests-with-playwright-ai-superpowers-9ia</link>
      <guid>https://dev.to/orliesaurus/tests-with-playwright-ai-superpowers-9ia</guid>
      <description>&lt;p&gt;This post is about a new plugin that we released to help Playwright users leverage AI Vision models to achieve more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZuJrjOIg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://i.imgflip.com/5rrexj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZuJrjOIg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://i.imgflip.com/5rrexj.gif" alt="Fast forward" width="360" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're in a rush you can skip the whole article and just jump to the code examples here: &lt;a href="https://github.com/dashcamio/goodlooks?tab=readme-ov-file#examples"&gt;https://github.com/dashcamio/goodlooks?tab=readme-ov-file#examples&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;End-to-end tests (E2E tests) simulate how a real user interacts with a web application, from opening the browser to clicking buttons and filling out forms.&lt;/p&gt;

&lt;p&gt;They aim to ensure the entire user journey functions as expected.&lt;/p&gt;

&lt;p&gt;However, sometimes tests pass, and sometimes they fail for seemingly random reasons. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;These are called flaky tests.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpzhll9wq72pa1dcjxzp5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpzhll9wq72pa1dcjxzp5.png" alt="E2E" width="600" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Flaky tests suck but there are many other things that are really hard to test. Here are some examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application State: If you click play on a video, has the video started playing?&lt;/li&gt;
&lt;li&gt;Image Contents: What's this image depicting? Does it make sense in the context of the title of the page?&lt;/li&gt;
&lt;li&gt;Responsiveness: Given a mobile design, does it match the mobile rendered version in the viewport.&lt;/li&gt;
&lt;li&gt;Accessibility: Is this specific UI element accessible according to best practices?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Flaky tests
&lt;/h2&gt;

&lt;p&gt;Developers love things that work or don't because they can fix things, but flaky tests…frustrating. They introduce uncertainty into the testing process. Test either pass or don't, they can't possibly only pass &lt;strong&gt;sometimes&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;And the cherry on top, you have to maintain a lot of tests through a framework…sometimes there are hundreds of tests and some are flaky…&lt;/p&gt;

&lt;p&gt;The worst type of flaky test are the E2E ones (End-2-End/End-to-End tests)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Example: Imagine testing a car. A unit test might focus on ensuring the engine starts, while an E2E test would simulate a whole driving experience. The E2E test provides a broader view but is more complex and prone to disruptions from external factors like traffic lights. What happens if the traffic light breaks? What happens if there's a massive hole on the road where you're driving the car?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffc0pm2owude99ghpy89c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffc0pm2owude99ghpy89c.png" alt="Ah, the good old testing dilemma" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing tests: enter testing frameworks
&lt;/h2&gt;

&lt;p&gt;Developers hate flaky tests, so to make things better, they invented tools to make their lives worse...I mean better.&lt;/p&gt;

&lt;p&gt;These tools manage the execution of tests. They will tell you whether a test has passed or failed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://playwright.dev/"&gt;Playwright&lt;/a&gt; is one of the many testing frameworks out there. Specifically, this one has exploded in popularity.&lt;/p&gt;

&lt;p&gt;It is specifically designed for web applications. It allows developers to write tests in JavaScript, Python, or TypeScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Playwright: why it makes sense and how can we make it better
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9hbsjv999aus44g53izn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9hbsjv999aus44g53izn.png" alt="Playwright is the new cool thing" width="660" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Playwright is one of the latest testing frameworks for web apps, with decent in-built reporting capability.&lt;/p&gt;

&lt;p&gt;It is a reliable and performant framework with support for multiple browsers, so you can install it and have the certainty that your tests will run on most browsers: write a test once and let it run in parallel through different browsers with almost no adjustments.&lt;/p&gt;

&lt;p&gt;As a tester, you know you must write and maintain tests regardless of the framework. There are some rules that need to be maintained.&lt;/p&gt;

&lt;h4&gt;
  
  
  Test executions needs to start from a fresh slate
&lt;/h4&gt;

&lt;p&gt;You can't start tests from a "dirty" environment, because that might impact how the test execution will be performed. Thus you will be able to set up a test environment as a user would experience it, the first time..and/or the N-th time around.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tests need to be updated
&lt;/h4&gt;

&lt;p&gt;Tests are often susceptible to changes in the application. Even minor UI tweaks, like button placement or text changes, can break the tests because they rely on specific interactions &lt;strong&gt;with CSS selectors&lt;/strong&gt;. This means you need to update the tests frequently to keep them working.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tests simulate real user journeys
&lt;/h4&gt;

&lt;p&gt;Tests can involve many steps and interact with various parts of the application. This complexity makes them harder to understand, debug, and modify than more focused unit tests…and sometimes on a user journey you encounter things that a computer might not be able to simulate.&lt;/p&gt;

&lt;p&gt;It…&lt;/p&gt;

&lt;p&gt;gets…&lt;/p&gt;

&lt;p&gt;complicated…&lt;/p&gt;

&lt;p&gt;👉 And we know that user journeys are complex. How do you test that a video autoplay, or doesn't autoplay on page load?&lt;/p&gt;

&lt;p&gt;👉 How do you ensure that the brand guidelines are respected?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvz65s4dh10zebgn50gxb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvz65s4dh10zebgn50gxb.png" alt="Before and after AI" width="469" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Extending Playwright with AI
&lt;/h2&gt;

&lt;p&gt;The more time is spent maintaining tests, the less time is spent on testing new features, and new user paths and ensuring the release of good quality software…&lt;/p&gt;

&lt;p&gt;Thus, flaky tests &lt;strong&gt;are time suckers for developers&lt;/strong&gt;: it takes time to investigate a flaky test and understand whether it is a false-positive or a real issue. When flaky tests are spotted, they might influence a whole set of tests that depend on them&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate Web Pages with AI + Natural Language
&lt;/h2&gt;

&lt;p&gt;Unreliable UI tests that break with every code change are really the worst. Some things that Playwright can't do well with test that require visual consistency. This can add a lot of extra work for QA and test developers.&lt;/p&gt;

&lt;p&gt;There are workarounds but they might take you down a horrible nightmare. A time-consuming nightmare. &lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing our Playwright plugin: Goodlooks
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--In3-Z7dV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://private-user-images.githubusercontent.com/318295/311263150-feb1d637-f1b0-48a2-8fd7-d4b855ad93bd.png%3Fjwt%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTA0NDAzODIsIm5iZiI6MTcxMDQ0MDA4MiwicGF0aCI6Ii8zMTgyOTUvMzExMjYzMTUwLWZlYjFkNjM3LWYxYjAtNDhhMi04ZmQ3LWQ0Yjg1NWFkOTNiZC5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwMzE0JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDMxNFQxODE0NDJaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT01NjA3M2NhMjRiMGViYjk5NGM0MGM5OGZiM2ZmNWMyZmVjM2Q0YzZjYzA3NDY2MDgyYWFlNmM3MjFhNjZlYjVkJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.kHXdtzVR4axrMLVQxj2ZU8ZigmsUjFvr7vExL7WwxH0" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--In3-Z7dV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://private-user-images.githubusercontent.com/318295/311263150-feb1d637-f1b0-48a2-8fd7-d4b855ad93bd.png%3Fjwt%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTA0NDAzODIsIm5iZiI6MTcxMDQ0MDA4MiwicGF0aCI6Ii8zMTgyOTUvMzExMjYzMTUwLWZlYjFkNjM3LWYxYjAtNDhhMi04ZmQ3LWQ0Yjg1NWFkOTNiZC5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwMzE0JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDMxNFQxODE0NDJaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT01NjA3M2NhMjRiMGViYjk5NGM0MGM5OGZiM2ZmNWMyZmVjM2Q0YzZjYzA3NDY2MDgyYWFlNmM3MjFhNjZlYjVkJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.kHXdtzVR4axrMLVQxj2ZU8ZigmsUjFvr7vExL7WwxH0" alt="Goodlooks" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/dashcamio/goodlooks"&gt;GoodLooks offers a revolutionary solution: visual validation with natural language.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's how simple it can be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//import playwright&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;//import goodlooks&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;goodlooks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;goodlooks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;//use this API key or register for your own&lt;/span&gt;
&lt;span class="nx"&gt;goodlooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zpka_c0d0539ada014283bc974f0fd55835ea_2b745cbf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;goodlooks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// write your first test that goes to a rick roll video on YouTube&lt;/span&gt;
&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rickroll&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.youtube.com/watch?v=dQw4w9WgXcQ&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// write in natural language the body of the test&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;goodlooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;video is not playing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The outcome should be a message similar to the following:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ PASS. The page shows a video with the play button available and a timeline that is not progressing, indicating that the video is currently not playing.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;The Problem with Static Tests&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Traditional UI testing relies on static selectors. But what happens when things change?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is that button really missing, or did the ID just change?&lt;/li&gt;
&lt;li&gt;Does the layout adapt seamlessly for mobile devices?&lt;/li&gt;
&lt;li&gt;Are the correct images displayed?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Validating these visual aspects with selectors alone is impossible. Manual testing is an option, but it's inefficient and prone to human error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Natural Language to the Rescue&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With an LLM, and more generally, AI, we can enable visual validation with natural language prompts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instead of writing code to target specific elements, you describe what you want to see on the page.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This approach offers several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Resilience to Code Changes:&lt;/strong&gt; Tests remain functional even if the underlying code structure evolves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus on Visual Quality:&lt;/strong&gt; Validate the actual appearance of your webpage, ensuring a consistent user experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplified Testing:&lt;/strong&gt; Write clear and concise prompts in plain English, eliminating the need for complex code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...and even more importantly, the ability to test things that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Testing subjective qualities like balance, alignment, and adherence to brand guidelines&lt;/strong&gt; - things that are difficult to capture with code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But with goodlooks it gets as simple as that:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;devices&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;goodlooks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;goodlooks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;goodlooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zpka_c0d0539ada014283bc974f0fd55835ea_2b745cbf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;goodlooks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ycombinator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://news.ycombinator.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;goodlooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;there is an orange strip at the top of the page&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Images content:&lt;/strong&gt; Playwright can easily verify if an image exists, but not necessarily if it's the correct one. With this approach you can fully recognize specific images and confirm they're displayed in the right places. This is particularly useful for e-commerce sites or applications with dynamic content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But with goodlooks you can simply do:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;devices&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;goodlooks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;goodlooks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;goodlooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zpka_c0d0539ada014283bc974f0fd55835ea_2b745cbf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;goodlooks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;correct image appears&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://eloquentjavascript.net/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;goodlooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;there is bird on this page&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility testing:&lt;/strong&gt; Playwright doesn't offer built-in features for accessibility testing, which ensures interfaces are usable by people with disabilities. You might need specialized accessibility testing tools.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  In conclusion the future of UI Testing has started already
&lt;/h2&gt;

&lt;p&gt;Test that flake are bad, but tests that are hard to maintain are worse.&lt;/p&gt;

&lt;p&gt;Even more so, the inability to test certain things is even worse and very time-consuming because it relies on manual testing.&lt;/p&gt;

&lt;p&gt;Obtaining feedback is crucial to ensure that users comprehend the content, can easily accomplish their objectives and tasks, and find your webpage appealing.&lt;/p&gt;

&lt;p&gt;Due to the costs and delays involved, running frequent manual tests can be a challenge, but AI can provide assistance whereas a year or so ago, this was much harder to implement.&lt;/p&gt;

&lt;p&gt;👉 Take a look at Goodlooks on Github: &lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/dashcamio"&gt;
        dashcamio
      &lt;/a&gt; / &lt;a href="https://github.com/dashcamio/goodlooks"&gt;
        goodlooks
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Visually Validate Playwright Tests Without Flaky Selectors
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/318295/311263150-feb1d637-f1b0-48a2-8fd7-d4b855ad93bd.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTA0NDEzODEsIm5iZiI6MTcxMDQ0MTA4MSwicGF0aCI6Ii8zMTgyOTUvMzExMjYzMTUwLWZlYjFkNjM3LWYxYjAtNDhhMi04ZmQ3LWQ0Yjg1NWFkOTNiZC5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwMzE0JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDMxNFQxODMxMjFaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0zOTEyN2UxODAzOGRhNDM5NGNhY2M1Mjk0N2U1Zjk0N2NiYWQzMTBmMWY1YjAzMjU3NGEyYjM0ZWRlMTU0ODQxJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.W4fsDv_9um8eUnqrTX_76OzVBdDW-u7M2ikHOSxuF70"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fuhVHHaq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://private-user-images.githubusercontent.com/318295/311263150-feb1d637-f1b0-48a2-8fd7-d4b855ad93bd.png%3Fjwt%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTA0NDEzODEsIm5iZiI6MTcxMDQ0MTA4MSwicGF0aCI6Ii8zMTgyOTUvMzExMjYzMTUwLWZlYjFkNjM3LWYxYjAtNDhhMi04ZmQ3LWQ0Yjg1NWFkOTNiZC5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwMzE0JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDMxNFQxODMxMjFaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0zOTEyN2UxODAzOGRhNDM5NGNhY2M1Mjk0N2U1Zjk0N2NiYWQzMTBmMWY1YjAzMjU3NGEyYjM0ZWRlMTU0ODQxJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.W4fsDv_9um8eUnqrTX_76OzVBdDW-u7M2ikHOSxuF70" alt="GoodLooks Logo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Visually Validate Playwright Tests Without Flaky Selectors&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Static selectors break with code changes and can't prove that a site "looks good". Is that button really missing or was the &lt;code&gt;id&lt;/code&gt; changed? Is the site responsive on mobile? Is the correct image showing? These kinds of tests are impossible to validate with selectors alone and take a lot of time to test manually. GoodLooks.ai lets you visually validate your web pages with natural language prompts instead of selectors.&lt;/p&gt;

&lt;p&gt;Check out our other products: &lt;a href="https://testdriver.ai/?ref=goodlooks" rel="nofollow"&gt;TestDriver.ai&lt;/a&gt; and &lt;a href="https://dashcam.io?ref=goodlooks" rel="nofollow"&gt;Dashcam.io&lt;/a&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quickstart&lt;/h2&gt;
&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;git clone git@github.com:dashcamio/goodlooks.git&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm install&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npx playwright test&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note that these examples use a demo key that gets rotated weekly; you'll want to &lt;a href="https://lgtm-main-80a621c.d2.zuplo.dev/docs/routes/~pricing" rel="nofollow"&gt;create your own API KEY&lt;/a&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Examples&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Element Visibility&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Validate that a cookie banner shows up.&lt;/p&gt;


&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;br&gt;
&lt;tbody&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;
&lt;br&gt;
&lt;strong&gt;Input&lt;/strong&gt; &lt;/td&gt; &lt;td&gt;&lt;strong&gt;Code&lt;/strong&gt;&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt; 

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/318295/311332999-4a4f02a4-95f8-4ec7-a0cb-f411c5d6776a.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTA0NDEzODEsIm5iZiI6MTcxMDQ0MTA4MSwicGF0aCI6Ii8zMTgyOTUvMzExMzMyOTk5LTRhNGYwMmE0LTk1ZjgtNGVjNy1hMGNiLWY0MTFjNWQ2Nzc2YS5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwMzE0JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDMxNFQxODMxMjFaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0yYzExZWE1OTFlNDYzMDY4N2YyYjI4Yjc4MzI5MDc1MzFlN2JjZWExYzdkMWYxNDIxMjNjNGZiZjFkZjU0ZGVmJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.pi6e2od7pJWHvemKJlMwL7Rq21ZmpS09lJ7CDCFUnSg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Uwzd-3IK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://private-user-images.githubusercontent.com/318295/311332999-4a4f02a4-95f8-4ec7-a0cb-f411c5d6776a.png%3Fjwt%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTA0NDEzODEsIm5iZiI6MTcxMDQ0MTA4MSwicGF0aCI6Ii8zMTgyOTUvMzExMzMyOTk5LTRhNGYwMmE0LTk1ZjgtNGVjNy1hMGNiLWY0MTFjNWQ2Nzc2YS5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwMzE0JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDMxNFQxODMxMjFaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0yYzExZWE1OTFlNDYzMDY4N2YyYjI4Yjc4MzI5MDc1MzFlN2JjZWExYzdkMWYxNDIxMjNjNGZiZjFkZjU0ZGVmJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.pi6e2od7pJWHvemKJlMwL7Rq21ZmpS09lJ7CDCFUnSg" alt="Framer.com Cookie Banner"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;

&lt;div class="highlight highlight-source-js notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; test&lt;span class="pl-kos"&gt;,&lt;/span&gt; expect &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;require&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"@playwright/test"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;goodlooks&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;require&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"goodlooks"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-s1"&gt;goodlooks&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/dashcamio/goodlooks"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4t2jpr7ga451jqsr49n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4t2jpr7ga451jqsr49n.png" alt="Image description" width="800" height="497"&gt;&lt;/a&gt;&lt;/p&gt;




</description>
    </item>
    <item>
      <title>Build your own Raycast extension, step by step, tutorial</title>
      <dc:creator>orliesaurus</dc:creator>
      <pubDate>Mon, 20 Nov 2023 23:36:27 +0000</pubDate>
      <link>https://dev.to/orliesaurus/build-your-own-raycast-extension-step-by-step-tutorial-5068</link>
      <guid>https://dev.to/orliesaurus/build-your-own-raycast-extension-step-by-step-tutorial-5068</guid>
      <description>&lt;p&gt;I’m a huge user of Raycast: I use it for everything from window resizing, to code snippets, to screenshots and shortcuts to my Github favorites.&lt;/p&gt;

&lt;p&gt;While I was creating my own extension Dashcam extension to capture videos, I thought, maybe I could write something so that anyone can learn how to make their extension!&lt;/p&gt;

&lt;p&gt;I think it would become handy, eventually. This guide explains how to create and debug your Raycast extension’s while you learn how its ecosystem works.&lt;/p&gt;

&lt;p&gt;All of what I wrote in this article can be achieved using 3 ingredients:&lt;br&gt;&lt;br&gt;
Raycast, your favorite code editor and the Dashcam screen recorder for developers!&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Intro&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.raycast.com/" rel="noopener noreferrer"&gt;Raycast&lt;/a&gt; is a fast, customizable macOS launcher that lets you quickly and easily create shortcuts to save time and make your workflow easier. This tiny, free app lets you open programs, search for files, and create keyboard shortcuts for literally anything! It has gained popularity as a powerful Spotlight alternative. This guide covers creating and publishing a Raycast extension from start to finish, adding a little oomph to traditional tutorials by leveraging videos and interactivity!&lt;/p&gt;

&lt;p&gt;I hope this can help anyone get started build their own extension.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Getting started with your extension&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We’re going to be using JavaScript to build our extension!&lt;/p&gt;

&lt;p&gt;I assume you have already downloaded and set up to Raycast. This guide complements, but does not replace, the official Raycast documentation – so if you’re a complete beginner &lt;a href="https://developers.raycast.com/" rel="noopener noreferrer"&gt;read it here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this article I will do a walkthrough of the full process of creating an extension from nada to hero!&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Create your project&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This is a Dash showing you how to create a project and extension in Raycast&lt;/p&gt;

&lt;p&gt;I created a folder where I am going to initiate my Raycast project. To achieve this, I simply created an empty folder somewhere on my drive that I can get to easily&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mkdir raycast-ext&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Initiate a new extension&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Next, as the Raycast documentation indicates, you have to create the scaffolding of your project.&lt;/p&gt;

&lt;p&gt;Open Raycast and Type &lt;strong&gt;Create Extension&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Create Extension – Developer command shows up, run it by clicking Open Command&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxmzazhcyslhd3u43xlvo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxmzazhcyslhd3u43xlvo.png" width="720" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This part enables the proper project structure to be created by the Raycast app itself.&lt;/p&gt;

&lt;p&gt;Then, go through the following screens and configure them as shown below.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Set up your extension details&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;These are the options I chose, but you can choose to pick whatever makes most sense for you:&lt;/p&gt;

&lt;p&gt;– Organization: &lt;strong&gt;None&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;– Unless you’re part of an Organization – most likely scenario is that you are not at first, &lt;a href="https://www.raycast.com/teams" rel="noopener noreferrer"&gt;this article explains what teams are&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;– Template: &lt;strong&gt;Detail&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;– Extension Name: &lt;strong&gt;Extension Demo&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;– Description: &lt;strong&gt;This is just a demo&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;– Categories: &lt;strong&gt;Fun&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;– Location: &lt;em&gt;Select the folder you created a moment ago&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;– Command name: &lt;strong&gt;Demo command&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;– Subtitle: &lt;strong&gt;Demo subtitle&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;– Description: &lt;strong&gt;This is just a test&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After completing these fields, create the extension by using the key combo&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⌘+Return
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;or click the Create extension text in the right corner&lt;/p&gt;

&lt;p&gt;Congratulations! You’ll see in the next screen that the extension has been successfully created and registered inside the Raycast app!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4k3hvbpn3qmy5q78e9pc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4k3hvbpn3qmy5q78e9pc.gif" alt="Easy, right?" width="240" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwekncs7ho5k0rwc9r6sc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwekncs7ho5k0rwc9r6sc.png" width="720" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Running the extension for the first time&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Next, let’s run the demo extension for the first time!&lt;/p&gt;

&lt;p&gt;To see your extension run for the first time, open the directory you created, locate the project, and run the command:&lt;/p&gt;

&lt;p&gt;Pretty cool!&lt;/p&gt;

&lt;p&gt;The extension is running, showing you a hello world message, the reason for that is that we chose a template in the previous step – we still haven’t touched the code!&lt;/p&gt;

&lt;p&gt;So let’s do that next.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Running and editing the template&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;To build your own extension, you can use Raycast’s own SDK, which includes set of UI components that you can use to assemble your extensions.&lt;/p&gt;

&lt;p&gt;Before we continue, get familiar with the Raycast SDK: There are multiple components in the SDK. You can find a listing here: &lt;a href="https://developers.raycast.com/api-reference/user-interface" rel="noopener noreferrer"&gt;https://developers.raycast.com/api-reference/user-interface&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another important thing is to become familiar with the extension folder structure: If you use an IDE editor like VScode, open your extension folder, and you should be presented with the following project structure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tiiswnc0n7flixgp1qi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tiiswnc0n7flixgp1qi.png" width="800" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, we’re ready to start developing our extension!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Inspecting the main file&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you open &lt;code&gt;src/index.tsx&lt;/code&gt; you will see the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;```
import { Detail } from "@raycast/api";

export default function Command() {

return &amp;lt;Detail markdown="# Hello World" /&amp;gt;;

}
```
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To test this command again in Raycast, ensure you’re running it with npm in your terminal.&lt;/p&gt;

&lt;p&gt;Type demo “Demo command” should show up and now hit return:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgh8tfb485xq8cpbb1qre.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgh8tfb485xq8cpbb1qre.png" width="800" height="586"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Building your own&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now let’s change it up: let’s build an extension that.. will fetch a random picture from a dog API, as it might bring a little happiness to the world!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2For0d5fhv7wru36s3jrf3.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2For0d5fhv7wru36s3jrf3.gif" width="200" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can call this extension “&lt;strong&gt;Dogjoy&lt;/strong&gt;” or something funny like that.&lt;/p&gt;

&lt;p&gt;Let’s create a new file called &lt;code&gt;fetchdata.tsx&lt;/code&gt; inside the &lt;code&gt;src&lt;/code&gt; folder&lt;/p&gt;

&lt;p&gt;We’re going to import &lt;code&gt;useFetch&lt;/code&gt; from the utils SDK and use it to call an endpoint.&lt;/p&gt;

&lt;p&gt;Specifically, it is the &lt;a href="https://dog.ceo" rel="noopener noreferrer"&gt;dog.ceo&lt;/a&gt; API endpoint which returns random pictures of dogs, one at a time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Installing Raycast helper library&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before we continue let’s raise a glass of your favorite cheering drink to ourselves&lt;/p&gt;

&lt;p&gt;👏&lt;/p&gt;

&lt;p&gt;We’re using the useFetch hook so this extension will use a helper library that we need to install called &lt;code&gt;@raycast/utils&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Using this helper library will allow us to use a custom React.js hook called &lt;code&gt;useFetch&lt;/code&gt;: using this hook, we can execute an API request to an endpoint.&lt;/p&gt;

&lt;p&gt;Let’s go ahead and install this library through npm by executing this in the root of our project:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install @raycast/utils&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Calling the API&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We specifically want to retrieve the &lt;code&gt;message&lt;/code&gt; – which corresponds to the URL of the image – like in the code below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    import { useFetch } from "@raycast/utils";

    export default function showDog () {

    console.log('Fetching API')

    return useFetch("https://dog.ceo/api/breeds/image/random").data.message;

    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we need to call this function from the main file&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Creating the command&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Let’s open &lt;code&gt;index.js&lt;/code&gt; and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    import { Detail } from "@raycast/api";

    import showDog from "./fetchdata";

    export default function Command() {

    return &amp;lt;Detail markdown={`

    ## Random 🐶

    ![](${showDog()})`} /&amp;gt;;

    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This imports the &lt;code&gt;showDog&lt;/code&gt; function from the &lt;code&gt;fetchdata&lt;/code&gt; file, then wraps it around the &lt;code&gt;Detail&lt;/code&gt; component, which renders, amongst other things, markdown!&lt;/p&gt;

&lt;p&gt;We use markdown to render the image because it’s fast!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Debugging with the help of Dashcam&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;It’s never easy to fix issues in your extension, in fact there’s a whole community effort on Raycast’s community Slack trying to help each other to fix them.&lt;/p&gt;

&lt;p&gt;While you’re developing, showing someone else what you were doing and went wrong can be quite powerful.&lt;/p&gt;

&lt;p&gt;When you run this extension for the first time in developer mode, you will notice that when you run your extension, there are multiple &lt;code&gt;console.log&lt;/code&gt; functions being called.&lt;/p&gt;

&lt;p&gt;You might wonder: is this a bug?&lt;/p&gt;

&lt;p&gt;The short answer is no. Here’s a Dash that shows you what I am talking about&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.dashcam.io/replay/64ee33bea4bc310061f566d0?share=DgA0e4gJRAjGwuTu244A" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flxm8w8zd2i1g80uvuyjp.gif" alt="4x fetch" width="640" height="616"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Watch &lt;a href="https://app.dashcam.io/replay/64ee33bea4bc310061f566d0?share=DgA0e4gJRAjGwuTu244A" rel="noopener noreferrer"&gt;4x fetch&lt;/a&gt; on Dashcam&lt;/p&gt;

&lt;p&gt;I shared this code with someone who understands really well how the render method behind &lt;code&gt;useFetch&lt;/code&gt; works, and they assured me that while the function might be re-rendered multiple times, the power of useFetch is that it only triggers one API call to your backend!&lt;/p&gt;

&lt;p&gt;Let’s now change the URL on line 5 to another one. this time we’re retrieving memes from imgflip’s API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    return useFetch("https://api.imgflip.com/get_memes").data.message;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run the extension again and see what happens!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.dashcam.io/replay/64ee66f13169ab006313dc84?share=frKJLPgJhLNqOpvGewg" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpch5voveu78d3omhfg13.gif" alt="imgflip error " width="640" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Watch &lt;a href="https://app.dashcam.io/replay/64ee66f13169ab006313dc84?share=frKJLPgJhLNqOpvGewg" rel="noopener noreferrer"&gt;imgflip error&lt;/a&gt; on Dashcam&lt;/p&gt;

&lt;p&gt;As you can see we’re getting an error because there is no object key named message.&lt;/p&gt;

&lt;p&gt;If we want to use this API we need to extract the URL by changing the code to look like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
    return useFetch("https://api.imgflip.com/get_memes").data?.data.memes[Math.floor(Math.random() * 100)].url;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This retrieves one meme at random from the imgflip meme API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using Dashcam to check for bugs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now you’re wondering, how can I verify that the API isn’t being called 4 times?&lt;/p&gt;

&lt;p&gt;Good question and here’s the answer&lt;/p&gt;

&lt;p&gt;You can verify this by replacing the URL on line 5 of &lt;code&gt;fetchdata.tsx&lt;/code&gt; with a local endpoint running on your machine (localhost) through a simple HTTP server .&lt;/p&gt;

&lt;p&gt;Let’s do this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.dashcam.io/replay/64ee35633169ab006313dc83?share=bJGP9qsZLSGhZMFQZENUqw" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzivdf5pagg848jxwij05.gif" alt="npm install" width="640" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Watch &lt;a href="https://app.dashcam.io/replay/64ee35633169ab006313dc83?share=bJGP9qsZLSGhZMFQZENUqw" rel="noopener noreferrer"&gt;npm install&lt;/a&gt; on Dashcam&lt;/p&gt;

&lt;p&gt;What I did is the following:&lt;/p&gt;

&lt;p&gt;I started a simple HTTP server and changed the fetch-able URL in &lt;code&gt;fetchdata.tsx&lt;/code&gt; file to call my local server.&lt;/p&gt;

&lt;p&gt;Then I look at the HTTP request logged by the local server and I see &lt;strong&gt;it’s only one.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Meanwhile the console.log has output 4 messages saying &lt;code&gt;Fetching API&lt;/code&gt; that means, that although it’s re-rendering 4x the API call is only called one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F36d8tf4sjmh67v0mjsta.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F36d8tf4sjmh67v0mjsta.gif" width="498" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see how by using Dashcam, I am sharing the full context of what I am doing, this is very beneficial when debugging with others!&lt;/p&gt;

&lt;p&gt;Now let’s put the files back to its initial state, so I can show some Dogs!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Commands&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In Raycast, you can have multiple commands. Your extension might be using them to achieve numerous things.&lt;/p&gt;

&lt;p&gt;In this extension we created we called initially the command “Demo” – let’s now change that so our extension is complete.&lt;/p&gt;

&lt;p&gt;You can change commands and its associated data through the &lt;code&gt;package.json&lt;/code&gt; file or the UI. I’ll do it in the editor – I am confident I can simply edit the data in this file and then reload the extension.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Publishing your extension: debugging build errors&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The last part of this tutorial is the most fun one, publishing your extension.&lt;/p&gt;

&lt;p&gt;To do this instead of running the code locally with &lt;code&gt;npm run dev&lt;/code&gt; you will have to run it through &lt;code&gt;npm run build&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will alert you if the code passes the build checks…&lt;/p&gt;

&lt;p&gt;Surprise, &lt;strong&gt;it’s not passing the checks YET!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So let’s go ahead and get fixing it&lt;/p&gt;

&lt;p&gt;First we’re going to add an interface&lt;/p&gt;

&lt;p&gt;The full code should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    import { useFetch } from "@raycast/utils";

    interface DogAPIResponse {

    message: string;

    }

    export default function showDog () {

    const { data } = useFetch&amp;lt;DogAPIResponse&amp;gt;("https://dog.ceo/api/breeds/image/random");

    return data?.message;

    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Publishing your extension onto the Raycast Github repo&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now that your extension is valid you can ship it to the Raycast extension repo, where all other extensions live!&lt;/p&gt;

&lt;p&gt;You can run &lt;code&gt;npm run publish&lt;/code&gt; to publish your extension now. You will be asked to authenticate with GitHub.&lt;/p&gt;

&lt;p&gt;Raycast creates an open a pull request in their &lt;a href="https://github.com/raycast/extensions" rel="noopener noreferrer"&gt;repository&lt;/a&gt;, on your behalf! &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncik9wymqxklbd7y8zto.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncik9wymqxklbd7y8zto.gif" width="498" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Hopefully, this article shows you how simple it is to create your first extension and publish it to the Raycast store.&lt;/p&gt;

&lt;p&gt;This is a simple extension, you can add much more – especially fall back and error catching… but hopefully this gives you a good introduction to building extensions.&lt;/p&gt;

&lt;p&gt;You’ve noticed that we had a couple of situations where we had to debug issues with the extension, luckily I was able to show you the issues using Dashcam to show you exactly how I solved them, it’s like a YouTube video but with better logs 😉 and context.&lt;/p&gt;

&lt;p&gt;If you want to learn more about how to create &amp;amp; publish extensions on Raycast &lt;a href="https://developers.raycast.com/basics/getting-started" rel="noopener noreferrer"&gt;feel free to browse the documentation here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’re interested in how Dashcam works and why it helps developers debug faster, you can find &lt;a href="https://www.dashcam.io/" rel="noopener noreferrer"&gt;further details on the Dashcam website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👉 If you just want a Raycast PRO code to try Raycast PRO for free, ask in the comments!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Everything I learned debugging my first Box app</title>
      <dc:creator>orliesaurus</dc:creator>
      <pubDate>Thu, 28 Sep 2023 17:36:13 +0000</pubDate>
      <link>https://dev.to/orliesaurus/everything-i-learned-debugging-my-first-box-app-49nb</link>
      <guid>https://dev.to/orliesaurus/everything-i-learned-debugging-my-first-box-app-49nb</guid>
      <description>&lt;p&gt;Writing your first Box app is sort of magical; in a couple of hours, you can go from having nothing to integrating with a bulletproof cloud filesystem.&lt;/p&gt;

&lt;p&gt;Wondering how I did it?&lt;/p&gt;

&lt;p&gt;Well, strap in because we're about to take a magical tour through the Box SDK and navigate through the ocean that is its first developer experience.&lt;/p&gt;

&lt;h2&gt;What we're trying to do?&lt;/h2&gt;

&lt;p&gt;I will show you how to overcome the most common issues with the Box Node.js SDK, when building something for the first time.&lt;/p&gt;

&lt;p&gt;By the end of this article, you'll be a certified Box ninja, able to effortlessly chop, kick, and grab data from Box with Javascript. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia4.giphy.com%2Fmedia%2F3ohhwtQGinneVw1FLy%2F200.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia4.giphy.com%2Fmedia%2F3ohhwtQGinneVw1FLy%2F200.gif" alt="ninja"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;
The Box SDK provides a sweet wrapper around the Box API, making incorporating fancy file features into your node apps simple.&lt;/p&gt;

&lt;p&gt;Like most beautiful things in life, however, there are some gotchas when getting started that you will learn by reading this article, and you will be glad that you did.&lt;/p&gt;

&lt;p&gt;By the way, I used &lt;a href="https://dashcam.io" rel="noopener noreferrer"&gt;Dashcam&lt;/a&gt; - the best screen recorder for developers - to take video clips of the flow and bugs discussed below!&lt;/p&gt;

&lt;h2&gt;Issue #1: Misconfigured Authorization Callback&lt;/h2&gt;

&lt;h3&gt;Problem:&lt;/h3&gt;

&lt;p&gt;When developing your Box app, after creating an Oauth2 authentication page, you might encounter an error saying &lt;strong&gt;Redirect URI mismatch,&lt;/strong&gt; as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.dashcam.io/replay/64fe2b1822b6230060e38376?share=4CJ1LTqGnADN0Qith82i8A" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplayable-api-production.herokuapp.com%2Freplay%2F64fe2b1822b6230060e38376%2Fgif%3FshareKey%3D4CJ1LTqGnADN0Qith82i8A" alt="URI mismatch"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Solution:&lt;/h3&gt;

&lt;p&gt;To solve this, please ensure that you have configured the redirect URI on the Box Developer platform to match the one URL in your code. This is the URL within your application that will receive OAuth 2.0 credentials and &lt;a href="https://app.box.com/developers/console/" rel="noopener noreferrer"&gt;can be configured within the configuration tab of your app here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.dashcam.io/replay/64fe2b7922b6230060e38377?share=Lodd0ck43YAmcP5MqYQPA" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplayable-api-production.herokuapp.com%2Freplay%2F64fe2b7922b6230060e38377%2Fgif%3FshareKey%3DLodd0ck43YAmcP5MqYQPA" alt="Solution to URI mismatch"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Issue #2: Wrong token&lt;/h2&gt;

&lt;h3&gt;Problem:&lt;/h3&gt;

&lt;p&gt;When trying to retrieve information (after successful authentication) with the Box API, I am hitting an error about the token being improperly formatted, as shown below.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Error occurred: AssertionError [ERR_ASSERTION]: tokenInfo is improperly formatted. Properties required: accessToken, refreshToken, accessTokenTTLMS and acquiredAtMS.&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href="https://app.dashcam.io/replay/64fe309022b6230060e38378?share=IsT3K3ZvV69342H2XMleg" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplayable-api-production.herokuapp.com%2Freplay%2F64fe309022b6230060e38378%2Fgif%3FshareKey%3DIsT3K3ZvV69342H2XMleg" alt="Error: tokenInfo improperly formatted"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Solution:&lt;/h3&gt;

&lt;p&gt;This error is caused by your secrets data: &lt;code&gt;CLIENT_ID, CLIENT_SECRET, SECRETKEY&lt;/code&gt; need to be defined as strings.&lt;/p&gt;

&lt;p&gt;Double-check and ensure that your token is wrapped in quotes in your &lt;strong&gt;.env&lt;/strong&gt; file to prevent this error!&lt;/p&gt;

&lt;h2&gt;Issue #3: Creating items - lack of permissions&lt;/h2&gt;

&lt;h3&gt;Problem:&lt;/h3&gt;

&lt;p&gt;When you try to create a folder or a file with the Box API you might encounter an API response similar to the below one and the file/folder does not get created.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;An error occurred Error: Unexpected API Response [403 Forbidden | .0a9157099d2d6c5f27336708e21f80f90]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href="https://app.dashcam.io/replay/64fe3f0ddaac9d00614ffa16?share=AAtkeHDspdFXWZquEdoWw" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplayable-api-production.herokuapp.com%2Freplay%2F64fe3f0ddaac9d00614ffa16%2Fgif%3FshareKey%3DAAtkeHDspdFXWZquEdoWw" alt="403 Forbidden - when creating a folder"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Solution:&lt;/h3&gt;

&lt;p&gt;To recover from this error, provide a higher auth scope to your app by navigating to the developer console and ensuring the &lt;strong&gt;"Write all files and folders stored in Box"&lt;/strong&gt; is selected. You can see the solution in the video embedded above. &lt;em&gt;Do not forget to restart your app and re-authenticate!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Below, you can see how the same issue happens when trying to upload a file:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.dashcam.io/replay/64fe4806daac9d00614ffa18?share=l7iZfxdMbAhdbpaweOWSw" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplayable-api-production.herokuapp.com%2Freplay%2F64fe4806daac9d00614ffa18%2Fgif%3FshareKey%3Dl7iZfxdMbAhdbpaweOWSw" alt="Upload a file: 403 Forbidden"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Issue #4: Issues with webhook&lt;/h2&gt;

&lt;p&gt;This is a fun one because it contains multiple levels of problems into one!&lt;/p&gt;

&lt;h3&gt;Problem A):&lt;/h3&gt;

&lt;p&gt;Using the Box API, you can modify files and folders and create a webhook that alert you when certain actions occur. You can create a webhook through the API, but sometimes you will face the following error when attempting to set it up&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Unexpected API Response [403 Forbidden | .0738507f87ab4740283c35d3701e5d9ad]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href="https://app.dashcam.io/replay/64fe50c1302ad00061eaf8d4?share=nrz2SH1fPDIwg3Ex5LuU9A" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplayable-api-production.herokuapp.com%2Freplay%2F64fe50c1302ad00061eaf8d4%2Fgif%3FshareKey%3Dnrz2SH1fPDIwg3Ex5LuU9A" alt="403 Forbidden - Webhooks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Solution A)&lt;/h3&gt;

&lt;p&gt;Most likely, you haven't given your Box App the permission to create webhooks.&lt;/p&gt;

&lt;p&gt;This setting differs from the ones that let you create files/folders.&lt;/p&gt;

&lt;p&gt;See here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.dashcam.io/replay/64fe519222b6230060e3837b?share=757Del92j4UT3S9W7pXCw" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplayable-api-production.herokuapp.com%2Freplay%2F64fe519222b6230060e3837b%2Fgif%3FshareKey%3D757Del92j4UT3S9W7pXCw" alt="Webhook: Fix permissions before creating a webhook"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Problem B)&lt;/h3&gt;

&lt;p&gt;When creating a webhook, even after having given your Box App the right permissions, you are facing a 400 error like the one below:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Unexpected API Response [400 Bad Request | nqr3b4hhsa30fy93.0a89df5b6bfbff030bc864b4d4c81e393] bad_request - Bad Request&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href="https://app.dashcam.io/replay/64fe529b302ad00061eaf8d5?share=MdSJgZ22S5Z6Qr36YWO8oA" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplayable-api-production.herokuapp.com%2Freplay%2F64fe529b302ad00061eaf8d5%2Fgif%3FshareKey%3DMdSJgZ22S5Z6Qr36YWO8oA" alt="Webhooks - Bad Request 400 - Missing httpS"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Solution B)&lt;/h3&gt;

&lt;p&gt;The Box API requires you to create a webhook that uses SSL: The notification URL specified in the address parameter must be a valid URL starting with &lt;strong&gt;HTTPS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example using Node.js&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;const result = await client.webhooks.create(
        '1303381221506', //my file id
        client.itemTypes.FILE,
        'https://some-endpoint-webhook.mydomain.com',
        [
            client.webhooks.triggerTypes.FILE.RENAMED,
            client.webhooks.triggerTypes.FILE.DOWNLOADED
        ])&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Problem C)&lt;/h3&gt;

&lt;p&gt;When you create a webhook for a specific item, you cannot create a second webhook that is associated with it. You will otherwise face the following error.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Unexpected API Response [409 Conflict | 7ejx8thhsa6jg839.05e8c637b1ef2870c7ff9ae16cfddaac8] conflict - Bad Request&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This means that we’re going to figure out another way…&lt;/p&gt;

&lt;h3&gt;Solution C)&lt;/h3&gt;

&lt;p&gt;The solution is to retrieve the existing webhook for this file/folder and then edit it. &lt;a href="https://github.com/box/box-node-sdk/blob/main/docs/webhooks.md#get-all-webhooks-information" rel="noopener noreferrer"&gt;More information is available from this Github repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also delete it first and then re-create it. There is no other way to modify an existing webhook at this moment.&lt;/p&gt;

&lt;p&gt;By the way - here's a &lt;a href="https://developer.box.com/guides/webhooks/triggers/" rel="noopener noreferrer"&gt;comprehensive list of actions the Box platform can send you a webhook for&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;This article showed you some of the most common issues and how to solve them with the support of video clips and error logs.&lt;/p&gt;

&lt;p&gt;This should help you overcome these initial blocks to your developer experience when using the Box SDK for Node.js.&lt;/p&gt;

&lt;p&gt;Getting started is pretty straightforward. The documentation is clear and has many examples- making it easy to get started. However if you face any issues, you can jump on the &lt;a href="https://forum.box.com/" rel="noopener noreferrer"&gt;Box community forums&lt;/a&gt; and ask for help. Their developer relations team is constantly monitoring it and ready to help you get rolling with any technical, SDK or API question! Alternatively reach out to the unofficial community on &lt;a href="https://stackoverflow.com/questions/tagged/box-api" rel="noopener noreferrer"&gt;Stackoverflow&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>node</category>
      <category>sdk</category>
      <category>videos</category>
      <category>code</category>
    </item>
    <item>
      <title>Made this meme but it's ONLY html and css</title>
      <dc:creator>orliesaurus</dc:creator>
      <pubDate>Sat, 09 Sep 2023 20:47:23 +0000</pubDate>
      <link>https://dev.to/orliesaurus/made-this-meme-but-its-only-html-and-css-44nk</link>
      <guid>https://dev.to/orliesaurus/made-this-meme-but-its-only-html-and-css-44nk</guid>
      <description>&lt;p&gt;I am pretty sure EVERYONE must have seen this meme** before...did you? (p.s. ⚠ turn the volume down):&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/5qHHm7ooavo"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;So, out of boredom I challenged my friend &lt;a href="https://twitter.com/air_framp_one" rel="noopener noreferrer"&gt;framp&lt;/a&gt; to see who would be faster at recreating it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Me with emacs or him with vim&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--0uG4dAm_--%2Fc_imagga_scale%2Cf_auto%2Cfl_progressive%2Ch_900%2Cq_auto%2Cw_1600%2Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg92vvaky0h0651372nxc.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--0uG4dAm_--%2Fc_imagga_scale%2Cf_auto%2Cfl_progressive%2Ch_900%2Cq_auto%2Cw_1600%2Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg92vvaky0h0651372nxc.jpeg" alt="vim or emacs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The result
&lt;/h3&gt;

&lt;p&gt;Well I don't know if it's because I suck at CSS or because my fingers are too slow but...&lt;/p&gt;

&lt;p&gt;...he won. &lt;/p&gt;

&lt;p&gt;Check it out 👇&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/framp/embed/XWoNLGe?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Smooth right? &lt;/p&gt;

&lt;p&gt;How much would you rate the implementation? &lt;/p&gt;

&lt;p&gt;See anything that could be improved?&lt;/p&gt;

&lt;p&gt;Leave a rating as a reply - could you do better?&lt;/p&gt;

&lt;p&gt;The ability to toggle and the animation using only CSS is mind blowing 🤯.&lt;/p&gt;

&lt;p&gt;P.S. It only took 4 hours hehehe xd&lt;br&gt;
** (source: tiktok &lt;a href="https://www.tiktok.com/@never.more17/video/7224470057282178305" rel="noopener noreferrer"&gt;https://www.tiktok.com/@never.more17/video/7224470057282178305&lt;/a&gt;) &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Debugging Typeform webhooks</title>
      <dc:creator>orliesaurus</dc:creator>
      <pubDate>Tue, 08 Aug 2023 18:06:19 +0000</pubDate>
      <link>https://dev.to/orliesaurus/debugging-typeform-webhooks-2m7j</link>
      <guid>https://dev.to/orliesaurus/debugging-typeform-webhooks-2m7j</guid>
      <description>&lt;p&gt;I love using Typeform to capture data from users, especially because it provides me with an EASY and truly SMOOTH user experience to capture data from users anywhere and everywhere. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ap2niukc66p8d7j002u.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ap2niukc66p8d7j002u.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a new submission is recorded in Typeform, I realized that the best way to poll to get the results was to use webhooks and not hammering the API every 15 minutes 🔨!&lt;/p&gt;

&lt;p&gt;However during the process of implementing a small webhook backend, I encountered quite some issues with the "verification" of the signature header.&lt;/p&gt;

&lt;p&gt;In this post, I will go over what I did to fix it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why webhooks?
&lt;/h2&gt;

&lt;p&gt;Now you might wondering - "but &lt;a href="https://twitter.com/sunglassesface" rel="noopener noreferrer"&gt;orlie&lt;/a&gt; why not just use an API"?!&lt;/p&gt;

&lt;p&gt;APIs are not the right choice for this because they require you to manually prompt them.&lt;/p&gt;

&lt;p&gt;They need to be invoked every time you want to check for data. &lt;/p&gt;

&lt;p&gt;On the other hand webhooks automatically send me data in response to a specific event.&lt;/p&gt;

&lt;p&gt;After learning that webhooks were natively supported by Typeform, I decided to dig right in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Typeform and webhooks
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/B4ZgcoPTHYXL2/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/B4ZgcoPTHYXL2/giphy.gif" alt="webhooks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can use Typeform webhooks to alert your application, or to connect it to an integration platform like &lt;a href="https://zapier.com" rel="noopener noreferrer"&gt;Zapier&lt;/a&gt;: the basic idea is that your application wants to get notified when certain event happens in Typeform without having to ask consistently.&lt;/p&gt;

&lt;p&gt;Here's what it would look like if I implemented it with an API pull strategy (N.B. read the graph starting from the bottom)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        Nope.
  ┌───────────────────────────┐
  │                           │
  │         Still nothing     │
  │ ┌───────────────────────┐ │
  │ │                       │ │
  │ │         No            │ │
  │ │ ┌───────────────────┐ │ │
  │ │ │                   │ │ │
┌─┴─┴─┴────┐         ┌────▼─▼─▼─┐
│ Typeform │         │My Backend│
└▲─▲──▲────┘         └───┬───┬─┬┘
 │ │  │                  │   │ │
 │ │  └──────────────────┘   │ │
 │ │Is there a new response? │ │
 │ │                         │ │
 │ │                         │ │
 │ └─────────────────────────┘ │
 │  Is there a new response?   │
 │                             │
 │                             │
 └─────────────────────────────┘
    Is there a new response?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of your app constantly polling Typeform to check if anything has changed, Typeform can use a webhook to push new data to your application when it is available.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          Here's a response
        ┌───────────────────┐
        │                   │
  ┌─────┴────┐         ┌────▼─────┐
  │ Typeform │         │My Backend│
  └────▲─────┘         └────┬─────┘
       │                    │
       └────────────────────┘
              thanks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This becomes specifically awesome because it allows you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Work in real-time:&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Webhooks allow data to be sent immediately when an event happens, without almost any delay.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Work more efficiently:&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;No need to write polling algorithms. This saves you time and resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stay flexible:&lt;/strong&gt; &lt;/li&gt;
&lt;li&gt;Webhooks can be used for many different types of applications and events. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are some examples of using webhooks with Typeform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When a new Typeform response is registered → send a webhook to my backend to retrieve that data and insert it into a queue&lt;/li&gt;
&lt;li&gt;When a new Typeform response is registered → send a webhook to Zapier to perform further automation with the data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Webhooks provide a simple webhook mechanism for different applications to communicate real-time data in an efficient way. Whether it's your backend or a middleware platform that enables you to run automation of any sort, webhooks are truly an integral part of your API plumbing toolkit, which is why they are commonly used for notifications, automation, and integrating modern services.&lt;/p&gt;

&lt;h2&gt;
  
  
  What can go wrong?
&lt;/h2&gt;

&lt;p&gt;Sometimes Webhooks fail to be delivered, or the payload content changes and thus it becomes difficult to parse the content.&lt;/p&gt;

&lt;p&gt;Using tools like &lt;a href="https://dashcam.io/" rel="noopener noreferrer"&gt;Dashcam&lt;/a&gt; and &lt;a href="https://ngrok.com/" rel="noopener noreferrer"&gt;ngrok&lt;/a&gt; you can understand when a webhook delivery has failed when working on your local development  environment, or during experimentation (like during a hackathon).&lt;/p&gt;

&lt;p&gt;Specifically Dashcam, a screen recorder for debugging makes it simple to find and fix bugs in your locally running apps. When you encounter an error, Dashcam will play back your screen in sync with terminal, logs, and network requests in a format called a "Dash."&lt;/p&gt;

&lt;p&gt;This form of debugging is known as time-travel debugging. Time Travel Debugging (TTD) can help you debug issues easier by letting you "rewind" your desktop, instead of having to reproduce the issue until you find the bug.&lt;/p&gt;

&lt;p&gt;This is important because as you develop your platform you want to add enough flexibility to catch errors in webhooks so that the rest of your code doesn't error out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.dashcam.io/replayable/log-and-error-tracking/video-tutorials-and-examples" rel="noopener noreferrer"&gt;If you want to learn how to setup Dashcam to create cool videos and catch bugs here's a mini guide&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to catch Webhooks errors
&lt;/h2&gt;

&lt;p&gt;Let's fire up our code editor and write some code to catch a webhook!&lt;/p&gt;

&lt;p&gt;We're gonna use Fastify as our library, so let's install it with &lt;code&gt;npm i fastify&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then let's create &lt;code&gt;index.js&lt;/code&gt; as our entry point&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fastify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fastify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
  &lt;span class="c1"&gt;//this enables the logger, good to have enabled while we develop locally&lt;/span&gt;
  &lt;span class="na"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Declare a route called /response to which we send a response when we parse the request&lt;/span&gt;
&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Run the server on port 5544!&lt;/span&gt;
&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5544&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; 
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's fire up our server!&lt;/p&gt;

&lt;p&gt;I'm using &lt;a href="https://ngrok.com/docs/getting-started/" rel="noopener noreferrer"&gt;ngrok&lt;/a&gt; to expose my localhost and port to the internet and I am logging any error to ngrok.log in the current directory where my code lives&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ngrok http 5544 --log=stdout &amp;gt; ngrok.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then once that's running I take the URL that ngrok gives you!&lt;/p&gt;

&lt;p&gt;Open up &lt;code&gt;ngrok.log&lt;/code&gt; and see what that address looks like, for me it was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://dd6e2439d50e.ngrok.app/response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally run&lt;/p&gt;

&lt;p&gt;&lt;code&gt;node index.js 2&amp;gt;&amp;amp;1 | tee -a typeform.log&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will write all errors to a log file called &lt;code&gt;typeform.log&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Typeform's webhooks
&lt;/h2&gt;

&lt;p&gt;Navigate into Typeform and add that &lt;a href="https://www.typeform.com/help/a/webhooks-360029573471/?tid=1cf4c0c7-8dc3-4317-b385-2cd91017e094" rel="noopener noreferrer"&gt;address as the Webhook configuration&lt;/a&gt; endpoint&lt;/p&gt;

&lt;p&gt;If you click the  &lt;code&gt;Send Test Request&lt;/code&gt; button, you should see an example request being sent to your backend&lt;/p&gt;

&lt;p&gt;Now if you go back to your terminal, you should see your code run and accept the example webhook payload!&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when you hit a 404 early on?
&lt;/h2&gt;

&lt;p&gt;Many times when you get started with Webhooks you configure it a little wrong…&lt;/p&gt;

&lt;p&gt;When I first ran the above code, I realized I forgot to add the name of the endpoint ( defined on &lt;code&gt;line 7&lt;/code&gt; in &lt;code&gt;index.js&lt;/code&gt;). The name of the endpoint is &lt;code&gt;/response&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Luckily I was running Dashcam so I caught the error on video and created a Dash below!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplayable-api-production.herokuapp.com%2Freplay%2F64c9679576b9bd0061ccb233%2Fgif%3FshareKey%3DVgMGYfuQjSu0TQy0gzqg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplayable-api-production.herokuapp.com%2Freplay%2F64c9679576b9bd0061ccb233%2Fgif%3FshareKey%3DVgMGYfuQjSu0TQy0gzqg" alt="Invalid Webhook Endpoint"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.dashcam.io/share/VgMGYfuQjSu0TQy0gzqg" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Watch on Dashcam&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;To solve this problem, I had to change the webhook address to actually be&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://dd6e2439d50e.ngrok.app/response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In the video, you will note the extra &lt;code&gt;s&lt;/code&gt; at the end of my path&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Debug your Webhooks further
&lt;/h2&gt;

&lt;p&gt;In this other example, I will add a security layer to the backend so that it will only accept webhooks if they're signed!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://www.typeform.com/help/a/webhooks-360029573471/?tid=1cf4c0c7-8dc3-4317-b385-2cd91017e094" rel="noopener noreferrer"&gt;To learn how to set up a signed webhook follow the instructions here, specifically step 9&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is secret key I used in my example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysupersecret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means that if another person sends data to my backend endpoint, without using my secret key as the hashing key, the backend will not process the data because it's not coming from Typeform!&lt;/p&gt;

&lt;p&gt;To do this, I change my backend to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fastify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fastify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
  &lt;span class="c1"&gt;//this enables the logger, good to have enabled while we develop locally&lt;/span&gt;
  &lt;span class="na"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;//require the crypto module to verify the signature&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Declare a route called /response to which we send a response when we parse the request&lt;/span&gt;
&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// This is where the magic happens, we take the whole body&lt;/span&gt;
  &lt;span class="c1"&gt;// turn it into a string and format it so it can be verified&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;typeform-signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;verifySignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Run the server on port 5544!&lt;/span&gt;
&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5544&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; 
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// This function verifies that it's a valid  &amp;amp; signed request&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;verifySignature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SECRET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;sign&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;`sha256=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I launch the app passing the secret as an environment with the following command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SECRET=mysupersecret node index.js dev 2&amp;gt;&amp;amp;1 | tee -a typeform.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the following happens:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplayable-api-production.herokuapp.com%2Freplay%2F64c988fb893f1d005fd5e930%2Fgif%3FshareKey%3DBBCYaJFo5app9A38kskaQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplayable-api-production.herokuapp.com%2Freplay%2F64c988fb893f1d005fd5e930%2Fgif%3FshareKey%3DBBCYaJFo5app9A38kskaQ" alt="Error secret"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://app.dashcam.io/share/BBCYaJFo5app9A38kskaQ" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Watch on Dashcam&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;If we inspect line &lt;code&gt;34&lt;/code&gt; to &lt;code&gt;40&lt;/code&gt; we can see that we're not doing anything strange….&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;BUT HERE's THE CATCH&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When we want to verify the signature and the hashed payload we need to add a new line to the request's body.&lt;/p&gt;

&lt;p&gt;To do this let's modify the code to add a new line so the hashed body and the signature will match&lt;/p&gt;

&lt;p&gt;check out line 16 of the code below to see what has changed&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fastify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fastify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
  &lt;span class="c1"&gt;//this enables the logger, good to have enabled while we develop locally&lt;/span&gt;
  &lt;span class="na"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;//require the crypto module to verify the signature&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Declare a route called /response to which we send a response when we parse the request&lt;/span&gt;
&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// This is where the magic happens, we take the whole body&lt;/span&gt;
  &lt;span class="c1"&gt;// turn it into a string and format it so it can be verified&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;typeform-signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;verifySignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;\n`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Run the server on port 5544!&lt;/span&gt;
&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5544&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; 
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// This function verifies that it's a valid  &amp;amp; signed request&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;verifySignature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SECRET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;sign&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;`sha256=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I run the code, the signature matches and the webhook is "accepted" and can be processed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplayable-api-production.herokuapp.com%2Freplay%2F64c98a26893f1d005fd5e931%2Fgif%3FshareKey%3DajrmlaAuluR3LQFJLi7pQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplayable-api-production.herokuapp.com%2Freplay%2F64c98a26893f1d005fd5e931%2Fgif%3FshareKey%3DajrmlaAuluR3LQFJLi7pQ" alt="It works"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://app.dashcam.io/share/ajrmlaAuluR3LQFJLi7pQ" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Watch on Dashcam&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You can see that using Typeform and Webhooks is truly a lifesaver.&lt;/p&gt;

&lt;p&gt;With a simple backend, you can capture data from Typeform and process the user's data in any way you desire.&lt;/p&gt;

&lt;p&gt;You don't have to poll Typeform for all the request and see if there's a new response added to your list.&lt;/p&gt;

&lt;p&gt;You also can use cryptographic methods to verify that the data you receive is truly from Typeform.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.dashcam.io/replayable/getting-started/introduction" rel="noopener noreferrer"&gt;If you want to learn how to setup Dashcam to create cool videos and catch bugs here's a mini guide&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typeform</category>
      <category>debugging</category>
      <category>dashcam</category>
      <category>webhooks</category>
    </item>
    <item>
      <title>I debugged and fixed Stripe Quickstart issues, so you don't have to</title>
      <dc:creator>orliesaurus</dc:creator>
      <pubDate>Thu, 03 Aug 2023 20:31:59 +0000</pubDate>
      <link>https://dev.to/orliesaurus/i-debugged-and-fixed-stripe-quickstart-issues-so-you-dont-have-to-4n1j</link>
      <guid>https://dev.to/orliesaurus/i-debugged-and-fixed-stripe-quickstart-issues-so-you-dont-have-to-4n1j</guid>
      <description>&lt;p&gt;Today I went through the Stripe Quickstart &lt;strong&gt;for the web (node + react)&lt;/strong&gt;. It wasn't a super smooth experienced, so if you also looked at this code and couldn't get it to work...I fixed it for all of us.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Qunl4fRh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pmfkqj0zgcypri2f9ox1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Qunl4fRh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pmfkqj0zgcypri2f9ox1.gif" alt="Debug Stripe, make money" width="768" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Quickstarts are awesome to get you from 0 to 100, very fast! 🏎️&lt;/p&gt;

&lt;p&gt;Sometimes, however, things change, and tutorials' code (or libraries) fall a little behind. That causes me (and everyone else) some friction.&lt;/p&gt;

&lt;p&gt;In this article, I will show you how I debugged the issues I found.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I am using &lt;a href="https://dashcam.io/?ref=devto"&gt;Dashcam&lt;/a&gt; to explain (and show you) the issues I faced. I captured them as they were happening and...I show you how I resolved them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I hope this unblocks you and help you build your first custom Stripe payment form!&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's start
&lt;/h2&gt;

&lt;p&gt;Navigate to the &lt;a href="https://stripe.com/docs/payments/quickstart"&gt;Stripe Quickstart page&lt;/a&gt; and download the zip file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ndqKPNPh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cae9g2domp12yqovxafk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ndqKPNPh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cae9g2domp12yqovxafk.png" alt="screenshot show button to download" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you download this, unzip it somewhere that makes it accessible for you.&lt;/p&gt;

&lt;p&gt;For me, that folder is &lt;code&gt;/Users/orlie/projects/stripe-sample-code/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once in there, you want to install the dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will then want to start the project by doing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I immediately saw an error. Let's see how to fix this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YEwYwwlK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://media0.giphy.com/media/nVTa8D8zJUc2A/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YEwYwwlK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://media0.giphy.com/media/nVTa8D8zJUc2A/giphy.gif" alt="" width="500" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Yarn installation not found - How to debug and fix it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IwBttLwA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://replayable-api-production.herokuapp.com/replay/64c16043dd259f0063b71d51/gif%3FshareKey%3D7P9UXCtEuug3gLCnWLIoA" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IwBttLwA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://replayable-api-production.herokuapp.com/replay/64c16043dd259f0063b71d51/gif%3FshareKey%3D7P9UXCtEuug3gLCnWLIoA" alt="npm start error" width="640" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.dashcam.io/replay/64c16043dd259f0063b71d51?share=7P9UXCtEuug3gLCnWLIoA" class="ltag_cta ltag_cta--branded"&gt;Watch missing yarn error on Dashcam&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;The error shown is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/bin/sh: yarn: &lt;span class="nb"&gt;command &lt;/span&gt;not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two solutions to this problem: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Either you install yarn&lt;/li&gt;
&lt;li&gt;You replace the entries in package.json to use npm, the default package runner&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will opt for the second option, so open up &lt;code&gt;package.json&lt;/code&gt; and edit lines 24 so it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"concurrently &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;npm run start-client&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;npm run start-server&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's rerun the app; you will notice it's finally working. JK - well, we're now hitting another error!&lt;/p&gt;

&lt;h2&gt;
  
  
  SSL Error
&lt;/h2&gt;

&lt;p&gt;This time the error has to do with SSL...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.dashcam.io/replay/64c16678dd259f0063b71d53?share=T9qPdODm15InGCv5hqKpw"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dePmuxIW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://replayable-api-production.herokuapp.com/replay/64c16678dd259f0063b71d53/gif%3FshareKey%3DT9qPdODm15InGCv5hqKpw" alt="SSL Error" width="640" height="416"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.dashcam.io/replay/64c16678dd259f0063b71d53?share=T9qPdODm15InGCv5hqKpw" class="ltag_cta ltag_cta--branded"&gt;Watch SSL Error on Dashcam&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;I found the solution after researching the error message (ChatGPT helps with this!):&lt;/p&gt;

&lt;p&gt;To fix the error I need to pin the &lt;code&gt;react-script&lt;/code&gt; library installed to a higher version (5 or higher):&lt;/p&gt;

&lt;p&gt;I go ahead and edit &lt;code&gt;package.json&lt;/code&gt; and change the line &lt;strong&gt;10&lt;/strong&gt; to look like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"react-scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^5.0.0"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That should fix the issue - let's give it another go!&lt;/p&gt;

&lt;p&gt;👉 Run &lt;code&gt;npm start&lt;/code&gt; again&lt;/p&gt;

&lt;h3&gt;
  
  
  Uncaught runtime error
&lt;/h3&gt;

&lt;p&gt;Relaunching the project using npm start - the localhost server goes up but...another error appears!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PMN4SAif--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://replayable-api-production.herokuapp.com/replay/64c18b6d1070e8006137e080/gif%3FshareKey%3DiF9GEYkDNnlsoeSmI2plQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PMN4SAif--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://replayable-api-production.herokuapp.com/replay/64c18b6d1070e8006137e080/gif%3FshareKey%3DiF9GEYkDNnlsoeSmI2plQ" alt="Runtime Error" width="640" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.dashcam.io/replay/64c18b6d1070e8006137e080?share=iF9GEYkDNnlsoeSmI2plQ" class="ltag_cta ltag_cta--branded"&gt;Watch Uncaught runtime Error&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;To make this error go away and continue with creating your first Stripe payment page, you can either comment out the  inside &lt;code&gt;CheckoutForm.jsx&lt;/code&gt;  or fix it.&lt;/p&gt;

&lt;p&gt;To fix, I changed the code in the following ways.&lt;br&gt;
First I changed the &lt;code&gt;&amp;lt;LinkAuthenticationElement&amp;gt;&lt;/code&gt; component to look like this in &lt;code&gt;CheckoutForm.jsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LinkAuthenticationElement&lt;/span&gt;
  &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;link-authentication-element&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleChange&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and secondly I added a function called &lt;code&gt;handleChange&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;  &lt;span class="nx"&gt;handleChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;setEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and voilà the code works!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--m_JrPKK4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://replayable-api-production.herokuapp.com/replay/64cc0defe0d92f00651b72f6/gif%3FshareKey%3D2XkSllBZRoHfP4MiZI2Rw" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m_JrPKK4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://replayable-api-production.herokuapp.com/replay/64cc0defe0d92f00651b72f6/gif%3FshareKey%3D2XkSllBZRoHfP4MiZI2Rw" alt="Stripe Payment Quickstart - Success!" width="640" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.dashcam.io/replay/64cc0defe0d92f00651b72f6?shareKey=2XkSllBZRoHfP4MiZI2Rw" class="ltag_cta ltag_cta--branded"&gt;Watch successful run on Dashcam&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I used Dashcam to catch errors
&lt;/h2&gt;

&lt;p&gt;Debugging software sucks, I get stuck, you get stuck, we start googling for answers - what a waste of time!&lt;br&gt;
Dashcam tracks errors so not only I can get help faster, but I can show exactly what caused an error to someone with full context!&lt;/p&gt;

&lt;p&gt;In this blogpost I use Dashcam. When I encountered any of these errors, Dashcam helps me play back the screen in sync with terminal, logs, and network requests. &lt;/p&gt;

&lt;p&gt;This form of debugging is known as time-travel debugging. I can "rewind" my desktop, instead of having to reproduce the issue!&lt;/p&gt;
&lt;h2&gt;
  
  
  How set up time travel debugging with Dashcam
&lt;/h2&gt;

&lt;p&gt;First every time I launch my app from the CLI, I use this command:&lt;br&gt;
&lt;code&gt;npm start 2&amp;gt;&amp;amp;1 | tee -a stripe-debug.log&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;What this command does is take all of the output that is shown on screen and write it using the &lt;code&gt;tee&lt;/code&gt; command to a file.&lt;br&gt;
In this example I called the file &lt;code&gt;stripe-debug.log&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Using this method you can use Dashcam to debug server Logs, you can use the Chrome extension to get console Logs from Chrome and Network Requests&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.dashcam.io/replayable/log-and-error-tracking/getting-started-with-logs" class="ltag_cta ltag_cta--branded"&gt;Learn how to set up Dashcam to debug your local dev environment&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>stripe</category>
      <category>debug</category>
      <category>dashcam</category>
    </item>
    <item>
      <title>I built a checkout button with vue-stripe</title>
      <dc:creator>orliesaurus</dc:creator>
      <pubDate>Mon, 24 Jul 2023 18:47:00 +0000</pubDate>
      <link>https://dev.to/orliesaurus/build-a-checkout-button-with-vue-stripe-3n5f</link>
      <guid>https://dev.to/orliesaurus/build-a-checkout-button-with-vue-stripe-3n5f</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;In this blogpost, I explain how I built a very simple product page using &lt;a href="https://vuestripe.com/" rel="noopener noreferrer"&gt;vue-stripe&lt;/a&gt;. If you decide to follow along, you can too  start selling your product or your services today!&lt;/p&gt;

&lt;p&gt;I am also going to share about how to resolve the most common problems while building this, using a tool called &lt;a href="https://dashcam.io" rel="noopener noreferrer"&gt;Dashcam&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why?
&lt;/h3&gt;

&lt;p&gt;Selling online is fun! If you have a project you want to monetize, this is the fastest way to get started!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxrahp870nh1gj444fn89.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxrahp870nh1gj444fn89.gif" alt="lets do this"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's get started! 🔥
&lt;/h2&gt;

&lt;p&gt;Let's assume you already have node.js and npm installed. If not, &lt;a href="https://www.digitalocean.com/community/tutorial-collections/how-to-install-node-js" rel="noopener noreferrer"&gt;you can learn how to do it here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first thing we want to do is set up the environment. We're going to be super vanilla and make a folder called:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

stripe-vue


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This will be the root directory, within which we're gonna initiate our Vue project!&lt;/p&gt;

&lt;p&gt;Go ahead and type the following command in your terminal of choice:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;mkdir &lt;/span&gt;stripe-vue &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;stripe-vue


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, we want to initiate a Vue project, so let's create a Vue app&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npm init vue@latest


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;What this does is, it initializes a new project with &lt;strong&gt;Vue.js&lt;/strong&gt; as a dependency using npm. &lt;/p&gt;

&lt;p&gt;I use &lt;code&gt;vue@latest&lt;/code&gt; - because &lt;code&gt;@latest&lt;/code&gt; indicates that the latest version of Vue.js should be installed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🤫&lt;br&gt;
(I use npm but if you're more advanced, or picky, you can choose your package manager of choice)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Configure your vue project
&lt;/h2&gt;

&lt;p&gt;When you run this command, &lt;code&gt;npm&lt;/code&gt; will prompt you to provide information about the project (e.g., project name, options, etc).&lt;br&gt;
&lt;code&gt;npm&lt;/code&gt; will generate a package.json file and install the latest version of Vue.js as a project dependency.&lt;br&gt;
Personally, I selected  &lt;strong&gt;No&lt;/strong&gt; on each option, because I am just building a simple checkout button, but your mileage may vary&lt;/p&gt;

&lt;p&gt;After this step, the CLI tells me to run some commands&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;cd &lt;/span&gt;vue-stripe-checkout
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;I am going to run the top 2 of them, &lt;strong&gt;but not the last one&lt;/strong&gt;, because I don't wanna run the project just yet...!&lt;/p&gt;

&lt;p&gt;The first two commands place me inside the actual project folder, where our code will go and install the dependencies.&lt;/p&gt;

&lt;p&gt;When that's finished I need one more dependency: &lt;code&gt;vue-stripe&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npm &lt;span class="nb"&gt;install&lt;/span&gt; @vue-stripe/vue-stripe


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Wait! 👋 Before you continue!&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://i.giphy.com/media/eeL8EcBBTwSMLACw6F/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/eeL8EcBBTwSMLACw6F/giphy.gif" alt="wait"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Create products and prices on Stripe and enable Checkout
&lt;/h3&gt;

&lt;p&gt;If you want to sell something you need to tell Stripe what you're selling.&lt;/p&gt;

&lt;p&gt;In this demo we're selling Jars of spicy condiment/ $15 a jar. It's delicious, I promise&lt;/p&gt;
&lt;h3&gt;
  
  
  Creating a product &amp;amp; set price
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3p0tpt5bmruhynnb65mq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3p0tpt5bmruhynnb65mq.png" alt="stripe product"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before we continue, we have to do something on Stripe's side. We have to create the product, and attach a price to it.  &lt;a href="https://support.stripe.com/questions/how-to-create-products-and-prices" rel="noopener noreferrer"&gt;There's a beautiful step by step tutorial on the Stripe Doc portal&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you have created your product with a price, you need to note down the &lt;strong&gt;SKU id&lt;/strong&gt;, you can find this on the Product settings page you just created and it looks like this &lt;code&gt;sku_OHSgXJ1rXvtqrt&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Using the SKU id value will tell Stripe's Checkout to find the default price of the product.&lt;/p&gt;
&lt;h3&gt;
  
  
  Creating a product price
&lt;/h3&gt;

&lt;p&gt;If you have multiple prices for the same product (perhaps you're offering different geo location/currencies) instead of noting down the &lt;strong&gt;SKU id&lt;/strong&gt; (red arrow), find the &lt;strong&gt;price id&lt;/strong&gt; (green arrow)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fslabstatic.com%2Fprod%2Fuploads%2Fvww16xdi%2Fposts%2Fimages%2F0_7SXCB5O-l-H9ba3cnBMmxZ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fslabstatic.com%2Fprod%2Fuploads%2Fvww16xdi%2Fposts%2Fimages%2F0_7SXCB5O-l-H9ba3cnBMmxZ.png" alt="screenshot of prices"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Ensuring checkout is enabled
&lt;/h3&gt;

&lt;p&gt;We also need to make sure that the checkout functionality is enabled, that's just a switch and it can be done quickly, &lt;a href="https://stripe.com/docs/payments/checkout/client#enable-checkout" rel="noopener noreferrer"&gt;the docs on Stripe's website clearly show the steps&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After creating the product, having attached a price to it (&lt;em&gt;and additionally another price&lt;/em&gt;), making sure checkout is enabled… now let's move on to creating the actual checkout page in Vue.&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating a vue component
&lt;/h2&gt;

&lt;p&gt;Back to our Vue project, let's make a new file in the components folder, if you followed this article step by step that should be:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

stripe-vue/vue-stripe-checkout/src/components/CheckoutButton.vue


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Now let's write some code.
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm32p0r2vso7d0zi5m9fc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm32p0r2vso7d0zi5m9fc.png" alt="write some code in vscode"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open the Checkout component and let's build a button that will lead to a Stripe checkout, first by importing &lt;code&gt;StripeCheckout&lt;/code&gt; from the &lt;code&gt;vue-stripe&lt;/code&gt; library&lt;/p&gt;

&lt;p&gt;Then we're going to export an object with the default export.&lt;/p&gt;

&lt;p&gt;We make &lt;code&gt;StripeCheckout&lt;/code&gt; as an exportable component. This way it can be rendered and used within our app!&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- We're going to put our Stripe Checkout code here in a moment --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StripeCheckout&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vue-stripe/vue-stripe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;StripeCheckout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Next we're going to write some code to customize our Stripe Checkout button.&lt;/p&gt;

&lt;p&gt;We're gonna write some code in this component within the &lt;code&gt;&amp;lt;template&amp;gt;&amp;lt;/template&amp;gt;&lt;/code&gt; section.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;stripe-checkout&lt;/span&gt;
        &lt;span class="na"&gt;ref=&lt;/span&gt;&lt;span class="s"&gt;"checkoutRef"&lt;/span&gt;
        &lt;span class="na"&gt;mode=&lt;/span&gt;&lt;span class="s"&gt;"payment"&lt;/span&gt;
        &lt;span class="na"&gt;:pk=&lt;/span&gt;&lt;span class="s"&gt;"publishableKey"&lt;/span&gt;
        &lt;span class="na"&gt;:line-items=&lt;/span&gt;&lt;span class="s"&gt;"lineItems"&lt;/span&gt;
        &lt;span class="na"&gt;:success-url=&lt;/span&gt;&lt;span class="s"&gt;"successURL"&lt;/span&gt;
        &lt;span class="na"&gt;:cancel-url=&lt;/span&gt;&lt;span class="s"&gt;"cancelURL"&lt;/span&gt;
        &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"v =&amp;gt; loading = v"&lt;/span&gt;
      &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Pay now!&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The interesting things here are to be noted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;mode&lt;/strong&gt;: This is the type of Checkout we're asking Stripe to create, either &lt;code&gt;payment&lt;/code&gt; or &lt;code&gt;subscription&lt;/code&gt;. We're making a one-time payment form, so let's set it to &lt;code&gt;payment&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;line-items:&lt;/strong&gt; The items that should be purchased by the customer, they're shown on the  Checkout interface and make up the total amount to be collected by Checkout.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;success-url:&lt;/strong&gt; The success URL to redirect the customer after purchase&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cancel-url:&lt;/strong&gt; The cancellation URL that we should redirect customers when payment is canceled (i.e. you click the back arrow on the Stripe checkout UI)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, let's add a button with a label saying "Pay now!" so we can purchase the Spicy jar condiment we're going to sell when clicking the button.&lt;br&gt;
Let's write some JavaScript to specify the config for our Stripe checkout that will appear once the button is clicked!&lt;/p&gt;
&lt;h3&gt;
  
  
  vue-stripe component setup
&lt;/h3&gt;

&lt;p&gt;Let's now hook up the component we have added with the ability to be purchased.&lt;/p&gt;

&lt;p&gt;We're going to set it up like this between the &lt;code&gt;&amp;lt;script&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt; tags&lt;/p&gt;

&lt;p&gt;Notice that we have to set up a couple of variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;publishableKey:&lt;/strong&gt; your PK from Stripe's developer dashboard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;lineItems:&lt;/strong&gt; an array of objects; each object has a &lt;em&gt;price&lt;/em&gt; for a product you're trying to sell and a &lt;em&gt;quantity&lt;/em&gt;. You can have multiple objects in this array to create more "complex" checkouts, for example, if you sell multiple products at once&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;succesURL&lt;/strong&gt;: The value of the URL after the user purchases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cancelURL&lt;/strong&gt;: The value of the URL if the user cancels&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StripeCheckout&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vue-stripe/vue-stripe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;StripeCheckout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;data &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;publishableKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;YOUR-STRIPE-PUBLIC-KEY&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;lineItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;YOUR-SKU-ID&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// The SKU id of the product created in the Stripe dashboard&lt;/span&gt;
            &lt;span class="c1"&gt;//price: '&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;YOUR&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;PRICE&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, // the Price id of the product I want to sell, if you have multiple prices, pick one

            quantity: 1,
          }
        ],
        successURL: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;URL_SUCCESS&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,
        cancelURL: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;URL_CANCEL&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,
      };
    },
    methods: {
      submit () {
        this.$refs.checkoutRef.redirectToCheckout();
      },
    },
  };
  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Now that we're done with the code, our final component should look like this:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;stripe-checkout&lt;/span&gt;
        &lt;span class="na"&gt;ref=&lt;/span&gt;&lt;span class="s"&gt;"checkoutRef"&lt;/span&gt;
        &lt;span class="na"&gt;mode=&lt;/span&gt;&lt;span class="s"&gt;"payment"&lt;/span&gt;
        &lt;span class="na"&gt;:pk=&lt;/span&gt;&lt;span class="s"&gt;"publishableKey"&lt;/span&gt;
        &lt;span class="na"&gt;:line-items=&lt;/span&gt;&lt;span class="s"&gt;"lineItems"&lt;/span&gt;
        &lt;span class="na"&gt;:success-url=&lt;/span&gt;&lt;span class="s"&gt;"successURL"&lt;/span&gt;
        &lt;span class="na"&gt;:cancel-url=&lt;/span&gt;&lt;span class="s"&gt;"cancelURL"&lt;/span&gt;
        &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"v =&amp;gt; loading = v"&lt;/span&gt;
      &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Pay now!&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StripeCheckout&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vue-stripe/vue-stripe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;StripeCheckout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;data &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;publishableKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;YOUR-STRIPE-PUBLIC-KEY&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;lineItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;YOUR-SKU-ID&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// The SKU id of the product created in the Stripe dashboard&lt;/span&gt;
            &lt;span class="c1"&gt;//price: '&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;YOUR&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;PRICE&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, // the Price id of the product I want to sell, if you have multiple prices, pick one

            quantity: 1,
          }
        ],
        successURL: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;URL_SUCCESS&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,
        cancelURL: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;URL_CANCEL&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,
      };
    },
    methods: {
      submit () {
        this.$refs.checkoutRef.redirectToCheckout();
      },
    },
  };
  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Display the Checkout component
&lt;/h2&gt;

&lt;p&gt;We're going to load our component into the &lt;code&gt;App.vue&lt;/code&gt; file so that it can be shown to our&lt;/p&gt;

&lt;p&gt;So create an &lt;code&gt;App.vue&lt;/code&gt; file (if one exists, delete the original one) and let's write some code&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CheckoutButton&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./components/CheckoutButton.vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Purchase a Spicy Jar&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;A Spicy Jar of condiment, delicious and adds a touch of fire to blander dishes&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"image"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Checkout&amp;gt;&amp;lt;/Checkout&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;style&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;#app&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;}&lt;/span&gt;
  &lt;span class="nf"&gt;#image&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;108px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;108px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;background-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url("https://stripe-camo.global.ssl.fastly.net/1da6f350ad1131277e51712ee1b3a992efb07a9333055c2926e8db1ceb633ab6/68747470733a2f2f66696c65732e7374726970652e636f6d2f6c696e6b732f4d44423859574e6a6446387851335673596e564762484e50633367355a58565366475a735833526c6333526663564a3463577075617a4a51654746784e57786857585a61656c4261633063793030657347736d436d4f")&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;style&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;First let's import our &lt;code&gt;Checkout&lt;/code&gt; element from the &lt;code&gt;CheckoutButton.vue&lt;/code&gt; file we created a moment ago, on line 2.&lt;/p&gt;

&lt;p&gt;Then within this file, let's add some context (line 7-9) so users know what they're buying and the &lt;code&gt;&amp;lt;Checkout&amp;gt;&amp;lt;/Checkout&amp;gt;&lt;/code&gt; component  (line 10). When this component renders, a button will be rendered.&lt;/p&gt;
&lt;h2&gt;
  
  
  Launching the Vue app
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftwozhg6z59bz7nit0cj5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftwozhg6z59bz7nit0cj5.png" alt="preview"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save all your files and head to your terminal:&lt;/p&gt;

&lt;p&gt;Make sure you're in the right directory &lt;code&gt;stripe-vue/vue-stripe-checkout&lt;/code&gt; and run &lt;code&gt;npm run dev&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Navigate to the URL and see your checkout button in action!&lt;/p&gt;
&lt;h2&gt;
  
  
  Debugging the Vue app
&lt;/h2&gt;

&lt;p&gt;What do you do when the code doesn't work?&lt;br&gt;
&lt;a href="https://i.giphy.com/media/1BXa2alBjrCXC/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/1BXa2alBjrCXC/giphy.gif" alt="debug"&gt;&lt;/a&gt;&lt;br&gt;
To debug the Vue app we're going to use &lt;a href="https://dashcam.io" rel="noopener noreferrer"&gt;Dashcam&lt;/a&gt; (disclaimer: I am working on Dashcam, a video-first code debugging tool for debs).&lt;/p&gt;

&lt;p&gt;Dashcam allows you to debug your work, it's a screen recorder app for debugging that gives you the error logs alongside a video of what caused them, all locally on your computer.&lt;/p&gt;

&lt;p&gt;Here's an example of me using Dashcam to debug the above code while I was writing this tutorial.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.dashcam.io/replay/64bab70f87ca2f005fd66f3d?share=QlZolztfFGyJQxDE5lUgXA" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freplayable-api-production.herokuapp.com%2Freplay%2F64bab70f87ca2f005fd66f3d%2Fgif%3FshareKey%3DQlZolztfFGyJQxDE5lUgXA" alt="Debug code"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://app.dashcam.io/replay/64bab70f87ca2f005fd66f3d?share=QlZolztfFGyJQxDE5lUgXA" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Watch it now&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;👀 Looking at the video I can see that I made a typo when pasting the API key (publishable one) that caused to Stripe checkout to not be able to be launched when I clicked the button.&lt;br&gt;
🫠 Easy fix!&lt;/p&gt;
&lt;h3&gt;
  
  
  Conclusion: Writing code and understanding errors
&lt;/h3&gt;

&lt;p&gt;In this article we learned how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;👀 Set up a simple Vue project from scratch&lt;/li&gt;
&lt;li&gt;🙏 Create a checkout button (some would call it a product page or a one-item cart page) that leads to a checkout page hosted by Stripe using Vue.&lt;/li&gt;
&lt;li&gt;🔥 Debug your web apps using video and error logs through Dashcam. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just looking at error logs sometimes is just not enough, especially when you're working with friends or team-mates asynchronously. &lt;/p&gt;

&lt;p&gt;Sharing a video is worth a million words. IMHO adding video evidence to error logs truly makes a difference: I truly believe debugging with Dashcam is pretty useful because it gives you the video context alongside the Dev Tools and Network errors.&lt;/p&gt;

&lt;p&gt;We'll see how to debug the backend in the next article!&lt;/p&gt;

&lt;p&gt;Thank you for reading this far and let me know if you have any comments or questions below this article!&lt;/p&gt;



&lt;p&gt;&lt;a href="https://www.dashcam.io/" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Download Dashcam (Free - Open Beta)&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vuestripe.com/" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Explore vue-stripe further&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>vue</category>
      <category>stripe</category>
      <category>debug</category>
    </item>
    <item>
      <title>6 things I do (and you should too) when I release open source code</title>
      <dc:creator>orliesaurus</dc:creator>
      <pubDate>Wed, 21 Dec 2022 22:33:40 +0000</pubDate>
      <link>https://dev.to/orliesaurus/6-things-i-do-and-you-should-too-when-i-release-open-source-code-8ne</link>
      <guid>https://dev.to/orliesaurus/6-things-i-do-and-you-should-too-when-i-release-open-source-code-8ne</guid>
      <description>&lt;p&gt;Every day 100+ open source libraries and projects are launched in the wild.&lt;/p&gt;

&lt;p&gt;Launching an open source project can be daunting, especially if you haven't done it before.&lt;/p&gt;

&lt;p&gt;Preparation + perseverance + luck are key to success here too - as much as anything else launched online.&lt;/p&gt;

&lt;p&gt;👉 Does IT work?&lt;/p&gt;

&lt;p&gt;The first thing you want to do before you open source something is to make sure - the project - WORKS and it is stable.&lt;br&gt;
Test your code in different environments and keep track of every step, you're gonna need it.&lt;/p&gt;

&lt;p&gt;👉 Write a good README.&lt;/p&gt;

&lt;p&gt;This is to me the most crucial experience of a good open source project. Be clear and aim to be successful by mentioning:&lt;/p&gt;

&lt;p&gt;Quick Get Started / HowTo&lt;br&gt;
Contributing guidelines&lt;br&gt;
License&lt;br&gt;
Project leads&lt;/p&gt;

&lt;p&gt;Add as much useful Documentation as you can. It's hard. I know.&lt;/p&gt;

&lt;p&gt;👉 Show immediate VALUE.&lt;/p&gt;

&lt;p&gt;A live-demo is worth a 1000 words. A good video/images are ok too! When people browse your project they want to immediately be captured and understand what you're sharing.&lt;/p&gt;

&lt;p&gt;👉 Write great DOCUMENTATION.&lt;/p&gt;

&lt;p&gt;It matters a lot, once readers are past your README, which should include the demo, they're going to jump and test it out immediately (if you made it easy to do so) and DIVE into the DOCUMENTATION to learn more about it.&lt;/p&gt;

&lt;p&gt;Pro-tip: Keeping Documentations up to date is hard, there are effective portals like that can help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VuePress&lt;/li&gt;
&lt;li&gt;Gitbook&lt;/li&gt;
&lt;li&gt;Docusaurus&lt;/li&gt;
&lt;li&gt;Readme(.com)&lt;/li&gt;
&lt;li&gt;Markdoc
Pick what you prefer and your most comfortable building upon.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 PROMOTION of your project.&lt;br&gt;
If you have an audience of thousands, leverage your audience to help you spread your project far and wide: word of mouth always beats other forms of promotion.&lt;/p&gt;

&lt;p&gt;If you do not have an audience - hard work by creating content and posts around the web.&lt;/p&gt;

&lt;p&gt;Other forms of promotion are talking at local meetups, conferences (online/offline), guest blogging and general writing for the purpose of SEO.&lt;/p&gt;

&lt;p&gt;Notice any pattern here? Does it feel a bit "marketing heavy"? That's because it is - be creative!&lt;/p&gt;

&lt;p&gt;Specifically on guest blogging if you wrote a tool to improve let's say online payments, find companies that might work in this vertical and suggest to talk about your open source project on their blog.&lt;/p&gt;

&lt;p&gt;👉 COMMUNITY &amp;amp; MAINTENANCE&lt;br&gt;
Every OS project needs maintainers, writing the code and letting it sit still does NOT work well in our always-changing world.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Updates/Vulnerabilities needs to be addressed&lt;/li&gt;
&lt;li&gt;Features can be added, not removed&lt;/li&gt;
&lt;li&gt;Issues need to be seen and triaged&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;MOST importantly the community of users that use and rely on your project needs to be nourished and helped.&lt;/p&gt;

&lt;p&gt;Whether it's entirely via your project's tools (i.e. Github discussions, a wiki, newsgroup) or a more social tool perhaps Discord, a forum, twitter..&lt;/p&gt;

&lt;p&gt;Here are some examples of great open source projects I collected as inspiration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/thelounge/thelounge#readme"&gt;https://github.com/thelounge/thelounge#readme&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/httpie/httpie"&gt;https://github.com/httpie/httpie&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/vue-stripe/vue-stripe"&gt;https://github.com/vue-stripe/vue-stripe&lt;/a&gt; by Joff Tiquez&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  opensource #maintain
&lt;/h1&gt;

</description>
      <category>opensource</category>
    </item>
    <item>
      <title>Writing great tickets in open source projects</title>
      <dc:creator>orliesaurus</dc:creator>
      <pubDate>Sun, 07 Aug 2022 16:58:45 +0000</pubDate>
      <link>https://dev.to/orliesaurus/writing-great-tickets-in-open-source-projects-1kj8</link>
      <guid>https://dev.to/orliesaurus/writing-great-tickets-in-open-source-projects-1kj8</guid>
      <description>&lt;p&gt;In the last couple of months, I have been working on a "new" open source project - &lt;a href="https://github.com/sfdc-stripe"&gt;an open source enterprise Stripe App&lt;/a&gt;! &lt;br&gt;
How can I improve collaboration when writing issues/tickets? Read more to learn my thoughts&lt;/p&gt;

&lt;h2&gt;
  
  
  The importance of writing good issue tickets on open source projects
&lt;/h2&gt;

&lt;p&gt;If you're working on an open source project (on github) with other people spread across the internet, it's important to write clear and concise issue tickets. &lt;/p&gt;

&lt;p&gt;This way, everyone knows what needs to be done and no one wastes time doing things that aren't necessary.&lt;/p&gt;

&lt;p&gt;There are a few different ways to go about writing an issue ticket. &lt;/p&gt;

&lt;p&gt;The first one: &lt;strong&gt;use a template.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GHYA5LEm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://media2.giphy.com/media/l0HepmE7eqiK86GSQ/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GHYA5LEm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://media2.giphy.com/media/l0HepmE7eqiK86GSQ/giphy.gif" alt="not bad of an idea" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Issue templates for open source projects
&lt;/h2&gt;

&lt;p&gt;You can find heaps of &lt;a href="https://github.com/stevemao/github-issue-templates"&gt;github issue templates online&lt;/a&gt; or in various text editors (like VS Code). Using a template ensures that you include all ✨ the important information in your ticket.✨&lt;/p&gt;

&lt;p&gt;However, templates can be a little dull. You might have to customize templates to match the type of project you're working on, so choose wisely.&lt;/p&gt;

&lt;p&gt;Another way to write an issue ticket is to simply include a title and brief description of the problem. This is usually sufficient for small issues. However, for larger issues, it's often helpful to include more information such as screenshots, steps to reproduce the problem, etc.&lt;/p&gt;

&lt;p&gt;In general, it's best to err on the side of &lt;strong&gt;too much information rather than too little&lt;/strong&gt;. That way, whoever is assigned the task can quickly understand the problem and get started on a fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to write a good issue ticket on an open source project
&lt;/h2&gt;

&lt;p&gt;There are a few best practices to follow. First, be sure to include clear and concise titles and descriptions. &lt;/p&gt;

&lt;p&gt;If your issue is related to a specific piece of code, include that in the ticket as well. It's also helpful to include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;logs&lt;/li&gt;
&lt;li&gt;screenshots &lt;/li&gt;
&lt;li&gt;.. or videos if applicable. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, be sure to tag the issue (do NOT tag the members of the project directly) so you can categorize appropriately the issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips for writing a good issue
&lt;/h2&gt;

&lt;p&gt;If you're new to open source, collaboration, or just want to improve your issue-reporting skills, read on for some tips on how to write a good issue ticket.&lt;/p&gt;

&lt;p&gt;1️⃣ First, be sure to include a clear and concise summary of the issue in the title. This will help others quickly understand what the problem is and whether they can help.&lt;/p&gt;

&lt;p&gt;2️⃣ Next, provide as much detail as possible in the body of the issue. Include screenshots or screencasts  if they would be helpful in understanding the problem.&lt;br&gt;
(Speaking of screencasts - my friend Ian is working on making screencasts awesome at &lt;a href="https://replayable.io"&gt;replayable.io&lt;/a&gt; if you want to give him some love - I would be appreciative of your time)&lt;/p&gt;

&lt;p&gt;👉 And be sure to include any relevant error messages or logs.&lt;/p&gt;

&lt;p&gt;3️⃣ Finally, if you have any ideas about how to fix the issue, feel free to include them in the issue ticket. It might help shape the solution, in a form or another - by the way, this is totally optional!&lt;/p&gt;




&lt;p&gt;Hope this helps, I've been using this method for &lt;a href="https://www.github.com/orliesaurus"&gt;my own work on Github&lt;/a&gt; and I think it has been working out pretty well!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>githubissues</category>
      <category>projectmanagement</category>
    </item>
    <item>
      <title>How do you monetize open source?</title>
      <dc:creator>orliesaurus</dc:creator>
      <pubDate>Wed, 03 Aug 2022 14:48:59 +0000</pubDate>
      <link>https://dev.to/orliesaurus/how-do-you-monetize-open-source-4g4c</link>
      <guid>https://dev.to/orliesaurus/how-do-you-monetize-open-source-4g4c</guid>
      <description>&lt;p&gt;Developers who have started dabbling in the open source space often wonder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can you live a normal life off of open source contributions?&lt;/li&gt;
&lt;li&gt;Can you build a business on top of open source software?&lt;/li&gt;
&lt;li&gt;Can you make open source software your business?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of the answers to the above questions are the same: yes but it's not easy.&lt;/p&gt;




&lt;p&gt;You might be wondering why are you writing this? Who on earth are you?&lt;/p&gt;

&lt;p&gt;I am &lt;a href="//orlie.dev"&gt;orlie&lt;/a&gt;, working on OSS and building in public.&lt;/p&gt;

&lt;p&gt;My goal is to create a profitable open source business with my best friend.&lt;/p&gt;

&lt;p&gt;For starters, we're building a Stripe App that is completely open source to sync data between Salesforce and Stripe! &lt;a href="https://github.com/sfdc-stripe"&gt;Curious? Check it out here&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;In over 15 years of building stuff online I have NEVER heard of someone who launched something from scratch into the open source world and started making money right away. &lt;/p&gt;

&lt;p&gt;Most times there's a story of long hours of grinding behind it. 🤯&lt;/p&gt;

&lt;p&gt;You might wonder, so &lt;strong&gt;how can I do it&lt;/strong&gt;? &lt;br&gt;
How can I monetize open source software I write?&lt;/p&gt;

&lt;p&gt;Glad you asked.&lt;/p&gt;

&lt;p&gt;Here are various ways to monetize yourself or business the open source way.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I have seen monetization succeed in open source
&lt;/h2&gt;

&lt;h4&gt;
  
  
  1. Donations &amp;amp; Sponsorships
&lt;/h4&gt;

&lt;p&gt;Perhaps the most common way to support open source project and their maintainers.&lt;br&gt;
For businesses it makes sense to sponsor development: it ensures the project stays stable, up-to-date and actively maintained. &lt;br&gt;
You can sponsor a whole project OR individually the people that work on the project.&lt;/p&gt;

&lt;p&gt;Regardless of the platform you're using to ask for recurring donations, if you're able to market your open source software or library to a community you can monetize...your community.&lt;br&gt;
Bootstrap, cURL, Vue.js and many other projects use this method.&lt;/p&gt;

&lt;p&gt;If you have $1 donation from 10000 people, that's $10,000 a month. You can use Github sponsors, OpenCollective or Patreon. There are many other&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Paid features as open source projects add-ons
&lt;/h4&gt;

&lt;p&gt;Easier said than done because most businesses do not start as open source projects. Those who do usually follow this path:&lt;br&gt;
1) They create an open source solution&lt;br&gt;
2) They realize there's a paid market&lt;br&gt;
3) They add extra features to the open source solution and charge for them&lt;br&gt;
Notable examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;Kong&lt;/li&gt;
&lt;li&gt;IntelliJ&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other businesses do the opposite, they take their closed source software and rebuild a light version for "community use"&lt;/p&gt;

&lt;h4&gt;
  
  
  3.Charge for running the software on behalf of your customers
&lt;/h4&gt;

&lt;p&gt;Simple enough business model once you create the infrastructure - as this can scale massively - you can get paid to manage your customer's instances of an open source software. &lt;/p&gt;

&lt;p&gt;Forum and blog engines like Ghost are a prime example of companies that managed to monetize open source by providing hosting. Another example is Bitbucket and Github's rival...Gitlab!&lt;/p&gt;

&lt;p&gt;👀 BTW, did you know that the software that runs dev.to is open-source but you will be soon able to pay for someone to run it for you:&lt;/p&gt;

&lt;h4&gt;
  
  
  4.Paid consulting and support
&lt;/h4&gt;

&lt;p&gt;Nginx PRO is a prime example of software which is open source practically but you can be paid to support the installation.&lt;br&gt;
You are not hosting the software for your customers but you're helping them configure it in a way that works best for them.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. A company hires you to maintain your project
&lt;/h4&gt;

&lt;p&gt;Some companies will hire developers/teams of devs to maintain an open source project on which they rely heavily&lt;/p&gt;

&lt;p&gt;In an ideal world if you write software and it gets used, you'd be able to capture some share of that value. But we're not there yet.&lt;/p&gt;

&lt;p&gt;In a great universe this model would be not needed, every company that is profitable (and non-profitable too) should:&lt;br&gt;
Create and maintain open source libraries&lt;br&gt;
Updating documentation, samples and tutorials&lt;br&gt;
Push forward the level of technology by innovating.&lt;/p&gt;

&lt;p&gt;But alas, we're far away from those models&lt;/p&gt;

</description>
      <category>opensource</category>
    </item>
    <item>
      <title>How I used an open source mentality to dig my path forward through school</title>
      <dc:creator>orliesaurus</dc:creator>
      <pubDate>Sat, 30 Jul 2022 19:48:00 +0000</pubDate>
      <link>https://dev.to/orliesaurus/how-i-used-an-open-source-mentality-to-dig-my-path-forward-through-school-5e8g</link>
      <guid>https://dev.to/orliesaurus/how-i-used-an-open-source-mentality-to-dig-my-path-forward-through-school-5e8g</guid>
      <description>&lt;p&gt;I like to believe I have been always involved in open source. Even as a kid and a student.&lt;/p&gt;

&lt;p&gt;When I was in school, I came up with an "open source"/"open collaboration" methodology to finish my homework fast with little effort. Back then I had no clue what open source was - but now I can find a parallel line of thinking between the two.&lt;/p&gt;

&lt;p&gt;Here is how it would work:&lt;br&gt;
Given a certain homework of 4 parts, I would start working on a part it, maybe do Part A) and B).&lt;/p&gt;

&lt;p&gt;Then I would ask someone else in my class to do part C) and D)&lt;/p&gt;

&lt;p&gt;Then ask someone else to verify all of A), B), C) and D) - did all parts make sense? &lt;/p&gt;

&lt;p&gt;If so, then great homework done for everyone. This applies to most homework in a way or another&lt;/p&gt;

&lt;p&gt;This could scale further...depending on the amount of work required.&lt;/p&gt;

&lt;p&gt;Everyone who'd have collaborated on the homework would at that point have the full answer.&lt;/p&gt;

&lt;p&gt;It was part of the process - if you're part of the "homework project", you get full access to the full homework.&lt;/p&gt;

&lt;p&gt;Strength in numbers?&lt;/p&gt;

&lt;p&gt;In the end,  before submission everyone would just take all of the work done as a group, change it a little bit &amp;amp; submit it.&lt;br&gt;
Worked. Every. Single. time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OVe6ctLt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.kym-cdn.com/photos/images/original/001/208/136/6b6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OVe6ctLt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.kym-cdn.com/photos/images/original/001/208/136/6b6.png" alt="Copy meme" width="880" height="877"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Homework done. Open source style.&lt;/p&gt;

&lt;p&gt;Was this legal at school? Probably not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Later in life
&lt;/h2&gt;

&lt;p&gt;In the world of software however, collaborating in plain sight is absolutely rewarded. &lt;/p&gt;

&lt;p&gt;Open source works pretty much just like that.&lt;/p&gt;

&lt;p&gt;I have a couple of open source projects under my belt, some a little successful other, not so much. (if you're interested check my Github &lt;a href="https://github.com/orliesaurus"&gt;here&lt;/a&gt;&lt;br&gt;
Open source to me, has always been an interesting concept:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I build something that I think is useful&lt;/li&gt;
&lt;li&gt;I put the code in the public domain&lt;/li&gt;
&lt;li&gt;Other people CAN use my code for free to solve their problems faster&lt;/li&gt;
&lt;li&gt;People can help me make the code better&lt;/li&gt;
&lt;li&gt;[Repeat this a few tens of times]&lt;/li&gt;
&lt;li&gt;Eventually, the next person that uses the project has a much better experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...but can someone make a living out of it?&lt;/p&gt;

&lt;p&gt;That's something I will explore in the next piece!&lt;/p&gt;

&lt;h2&gt;
  
  
  What about you?
&lt;/h2&gt;

&lt;p&gt;🤔 Have you worked hard through school, or did you work smart?&lt;br&gt;
Was the approach you took worth it?&lt;br&gt;
Let me know in the comments!&lt;/p&gt;

</description>
      <category>opensource</category>
    </item>
    <item>
      <title>Just released an open source Stripe App</title>
      <dc:creator>orliesaurus</dc:creator>
      <pubDate>Mon, 18 Jul 2022 20:45:40 +0000</pubDate>
      <link>https://dev.to/orliesaurus/just-released-an-open-source-stripe-app-5f6d</link>
      <guid>https://dev.to/orliesaurus/just-released-an-open-source-stripe-app-5f6d</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;So I just open sourced something, and now what you may ask....?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're familiar with Stripe, you might have heard that they've recently announced the Stripe &lt;a href="https://marketplace.stripe.com/"&gt;app store&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mZ96qFTT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fzn2n1nzq965/1qmk7YyvOo147CMfFxjtta/964116e441a62e797d3c8683df9115f1/dashboard-app-view6.latin.png%3Fq%3D80%26fm%3Dwebp%26w%3D1620" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mZ96qFTT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fzn2n1nzq965/1qmk7YyvOo147CMfFxjtta/964116e441a62e797d3c8683df9115f1/dashboard-app-view6.latin.png%3Fq%3D80%26fm%3Dwebp%26w%3D1620" alt="Stripe Apps" width="880" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yes! You can now build apps and let 3rd party users install them on their Stripe dashboard - and yes, you can ✨ monetize it ✨.&lt;/p&gt;

&lt;p&gt;I have recently pushed an open source Stripe to Github so others can learn how to build on top of the Stripe marketplace.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://github.com/sfdc-stripe" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://res.cloudinary.com/practicaldev/image/fetch/s--5nHZ0085--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://avatars.githubusercontent.com/u/106834770%3Fs%3D280%26v%3D4" height="280" class="m-0" width="280"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://github.com/sfdc-stripe" rel="noopener noreferrer" class="c-link"&gt;
          SFDC Insights for Stripe Apps · GitHub
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          An open-source Salesforce integration that shows your instance's customers insights inside the Stripe Dashboard - SFDC Insights for Stripe Apps
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://res.cloudinary.com/practicaldev/image/fetch/s--uPIa4SpL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.githubassets.com/favicons/favicon.svg" width="32" height="32"&gt;
        github.com
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;It's actually not simple to create open source projects and if you're not going to put efforts in it, it might be a wasted effort (your time, energy, resources etc) and it will fail.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;That's when reality kicked in&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I felt like I had a good idea, but will I be able to be successful in growing interest and an audience around this project?&lt;/p&gt;

&lt;h3&gt;
  
  
  Why creating an open source Stripe?
&lt;/h3&gt;

&lt;p&gt;I was actually shocked there wasn't any end-to-end open source Stripe app to inspire others.&lt;/p&gt;

&lt;p&gt;I thought this was a big enough gap where it would make sense that I would be able to create something that would help the wider community.&lt;/p&gt;

&lt;p&gt;First of all, I planned this would take 2 sprints, I set aside evenings and started building.&lt;/p&gt;

&lt;p&gt;It all started with the Stripe tutorial and this commands&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stripe plugin &lt;span class="nb"&gt;install &lt;/span&gt;apps &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; stripe apps create helloworld
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Building the app
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ILUDBMSt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1488229297570-58520851e868%3Fixlib%3Drb-1.2.1%26ixid%3DMnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8%26auto%3Dformat%26fit%3Dcrop%26w%3D2669%26q%3D80" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ILUDBMSt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1488229297570-58520851e868%3Fixlib%3Drb-1.2.1%26ixid%3DMnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8%26auto%3Dformat%26fit%3Dcrop%26w%3D2669%26q%3D80" alt="Entering the app writing" width="880" height="588"&gt;&lt;/a&gt;&lt;br&gt;
I knew I wanted to create something useful, and if you want to build something useful you have to start building something that alleviates someone's pain-point. &lt;/p&gt;

&lt;p&gt;They say the idea is the most important thing...not always!&lt;br&gt;
This is how I came up with it: I hate looking up data between tabs. Copy-pasting is fun, so anything that makes a boring and repetitive process better is a pot of gold for me!&lt;/p&gt;

&lt;p&gt;Next I had to find a target: There are thousands of companies out there but I picked the largest CRM as the subject of what I was going to build: an integration between Salesforce and Stripe.&lt;/p&gt;

&lt;p&gt;From there, I started writing code.&lt;/p&gt;

&lt;p&gt;The idea is that any other app developers can take inspiration from this end-to-end implementation and build on top of it, fork it, improve it or ignore the project!&lt;/p&gt;

&lt;h3&gt;
  
  
  Small steps to open sourcing.
&lt;/h3&gt;

&lt;p&gt;First of all, I started by committing &lt;em&gt;ALL the code&lt;/em&gt; needed to run the app successfully.&lt;/p&gt;

&lt;p&gt;I then added a &lt;code&gt;README.md&lt;/code&gt; file that hopefully will help people understand how to best use the front-end and back-end of the app.&lt;/p&gt;

&lt;p&gt;However the list of things to add to make this open source project more successful are still a lot.&lt;/p&gt;

&lt;p&gt;✅ Create a Github org for the project&lt;br&gt;
✅ Push code to the repository on Github&lt;br&gt;
✅ Write a quality README.MD&lt;br&gt;
🔳 Write a CHANGELOG.MD&lt;br&gt;
🔳 Write a CONTRIBUTING.MD&lt;br&gt;
✅ Choose an open source license&lt;br&gt;
🔳 Write tests&lt;br&gt;
🔳 Create label for Github issues&lt;/p&gt;

&lt;h3&gt;
  
  
  Going forward
&lt;/h3&gt;

&lt;h2&gt;
  
  
  I hope I didn't break any ToS and that I can keep improving the code, I have a lot of work ahead of me to make this project successful but I hope that by building it in public it will help keep me accountable AND inspire others to hopefully open source some of their code for the first time, or maybe just to contribute to my project!
&lt;/h2&gt;

&lt;p&gt;If you want to learn more, &lt;a href="https://twitter.com/sunglassesface"&gt;follow me on Twitter&lt;/a&gt; and/or &lt;a href="//https:/www.github.com/orliesaurus"&gt;Github&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>stripe</category>
      <category>opensource</category>
      <category>github</category>
    </item>
  </channel>
</rss>
