<?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: Tim Geyssens</title>
    <description>The latest articles on DEV Community by Tim Geyssens (@timgeyssens).</description>
    <link>https://dev.to/timgeyssens</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%2F451900%2F562018d9-1011-448d-8aa1-ab15640ad10d.jpeg</url>
      <title>DEV Community: Tim Geyssens</title>
      <link>https://dev.to/timgeyssens</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/timgeyssens"/>
    <language>en</language>
    <item>
      <title>One-pager powered by json content files and synced to git</title>
      <dc:creator>Tim Geyssens</dc:creator>
      <pubDate>Mon, 15 Sep 2025 18:15:58 +0000</pubDate>
      <link>https://dev.to/timgeyssens/one-pager-powered-by-json-content-files-and-synced-to-git-3c8j</link>
      <guid>https://dev.to/timgeyssens/one-pager-powered-by-json-content-files-and-synced-to-git-3c8j</guid>
      <description>&lt;p&gt;Recently I took &lt;a href="https://www.warp.dev/" rel="noopener noreferrer"&gt;Warp&lt;/a&gt; for a testdrive. I have a couple of friends who have a &lt;a href="https://denberendries.be/" rel="noopener noreferrer"&gt;eco-glamping&lt;/a&gt; and where in need of a website. So perfect opportunity to see the current state of AI in webdev.&lt;/p&gt;

&lt;p&gt;And colour me impressed. I was able to create a one pager powered by a tailored CMS. The frontend is plain html/css/js powered by json files that are managed by the php backend.&lt;/p&gt;

&lt;p&gt;Now as an additional challenge I wanted to make sure the content was also source controlled. Every *.json change and updated file in /images (the path where user uploaded files are stored) should be stored in git.&lt;/p&gt;

&lt;p&gt;It took me a couple of prompts to end up with a php scripts that uses the github content api to check the status and if necessary update the content files. &lt;/p&gt;

&lt;p&gt;Running this as a scheduled task on a Plesk interface from a cheap hosting solution.&lt;/p&gt;

&lt;p&gt;`&lt;br&gt;&lt;br&gt;
    &amp;lt;?php&lt;br&gt;
    /**&lt;br&gt;
     * GitHub Sync Script for DenBerendries - Using Contents API&lt;br&gt;
     * &lt;br&gt;
     * This script uses the GitHub Contents API which handles encoding automatically&lt;br&gt;
     * and syncs both JSON files in root and images in /images directory&lt;br&gt;
     */&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Configuration
$GITHUB_TOKEN = '???';
$GITHUB_USERNAME = 'timgeyssens';
$GITHUB_REPO = 'DenBerendries';
$BRANCH = 'main';
$COMMIT_MESSAGE = 'Auto commit from server on ' . date('Y-m-d H:i:s');

// Set content type to JSON
header('Content-Type: application/json');

// Check if script is called via HTTP or CLI
$isHttp = (php_sapi_name() !== 'cli');

// Function to make GitHub API requests
function githubApiRequest($url, $token, $method = 'GET', $data = null) {
    $ch = curl_init();
    $headers = [
        'Authorization: token ' . $token,
        'User-Agent: PHP-GitHub-Sync-Script',
        'Content-Type: application/json',
    ];

    curl_setopt($ch, CURLOPT_URL, 'https://api.github.com' . $url);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);

    if ($method === 'POST') {
        curl_setopt($ch, CURLOPT_POST, true);
        if ($data) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    } elseif ($method === 'PUT') {
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
        if ($data) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    } elseif ($method === 'PATCH') {
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
        if ($data) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    }

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    return ['code' =&amp;gt; $httpCode, 'body' =&amp;gt; json_decode($response, true)];
}

// Function to find JSON files in root and images in /images directory
function findFilesToSync() {
    $files = [];
    $basePath = realpath('.');

    // Supported image extensions
    $imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp'];

    // 1. Find JSON files in root directory
    $rootIterator = new DirectoryIterator($basePath);
    foreach ($rootIterator as $file) {
        if ($file-&amp;gt;isFile() &amp;amp;&amp;amp; !$file-&amp;gt;isDot()) {
            $extension = strtolower(pathinfo($file-&amp;gt;getFilename(), PATHINFO_EXTENSION));

            // Check if it's a JSON file
            if ($extension === 'json') {
                // Get relative path from the base directory
                $relativePath = substr($file-&amp;gt;getRealPath(), strlen($basePath) + 1);

                // Normalize path separators for GitHub
                $relativePath = str_replace('\\', '/', $relativePath);

                $files[] = $relativePath;
            }
        }
    }

    // 2. Find image files in /images directory
    $imagesPath = $basePath . DIRECTORY_SEPARATOR . 'images';
    if (is_dir($imagesPath)) {
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($imagesPath, RecursiveDirectoryIterator::SKIP_DOTS),
            RecursiveIteratorIterator::SELF_FIRST
        );

        foreach ($iterator as $file) {
            if ($file-&amp;gt;isFile()) {
                $extension = strtolower(pathinfo($file-&amp;gt;getFilename(), PATHINFO_EXTENSION));

                // Check if it's an image file
                if (in_array($extension, $imageExtensions)) {
                    // Get relative path from the base directory
                    $relativePath = substr($file-&amp;gt;getRealPath(), strlen($basePath) + 1);

                    // Normalize path separators for GitHub
                    $relativePath = str_replace('\\', '/', $relativePath);

                    $files[] = $relativePath;
                }
            }
        }
    }

    return $files;
}

// Function to get file SHA from GitHub
function getFileShaFromGitHub($token, $username, $repo, $branch, $filePath) {
    $url = "/repos/$username/$repo/contents/$filePath?ref=$branch";
    $response = githubApiRequest($url, $token);

    if ($response['code'] === 200) {
        return $response['body']['sha'];
    }

    return null;
}

// Function to sync files using Contents API
function syncToGitHub($token, $username, $repo, $branch, $commitMessage) {
    $result = [];

    // Find all files to sync
    $allFiles = findFilesToSync();
    $result['all_files'] = count($allFiles);

    if (count($allFiles) === 0) {
        return ['success' =&amp;gt; true, 'message' =&amp;gt; 'No files found to sync'];
    }

    $changedFiles = [];
    $skippedFiles = [];

    foreach ($allFiles as $file) {
        if (file_exists($file)) {
            $localContent = file_get_contents($file);
            $localSha = sha1("blob " . strlen($localContent) . "\0" . $localContent);

            // Get the remote SHA
            $remoteSha = getFileShaFromGitHub($token, $username, $repo, $branch, $file);

            // If file doesn't exist in remote or SHA is different, it's changed
            if (!$remoteSha || $remoteSha !== $localSha) {
                $changedFiles[] = $file;
            } else {
                $skippedFiles[] = $file;
            }
        }
    }

    $result['changed_files'] = count($changedFiles);
    $result['skipped_files'] = count($skippedFiles);
    $result['files'] = $changedFiles;

    // If no files changed, return early without creating a commit
    if (count($changedFiles) === 0) {
        return ['success' =&amp;gt; true, 'message' =&amp;gt; 'No changes to commit', 'action' =&amp;gt; 'none'];
    }

    // Update each changed file
    $successCount = 0;
    $errorCount = 0;
    $errors = [];

    foreach ($changedFiles as $file) {
        if (file_exists($file)) {
            $content = file_get_contents($file);
            $sha = getFileShaFromGitHub($token, $username, $repo, $branch, $file);

            $data = [
                'message' =&amp;gt; $commitMessage,
                'content' =&amp;gt; base64_encode($content),
                'branch' =&amp;gt; $branch
            ];

            // If file exists, include the SHA for update
            if ($sha) {
                $data['sha'] = $sha;
            }

            $response = githubApiRequest("/repos/$username/$repo/contents/$file", $token, 'PUT', $data);

            if ($response['code'] === 200 || $response['code'] === 201) {
                $successCount++;
            } else {
                $errorCount++;
                $errors[] = "Failed to update $file: " . json_encode($response['body']);
            }
        }
    }

    if ($errorCount &amp;gt; 0) {
        $result['success'] = false;
        $result['message'] = "Completed with $successCount successes and $errorCount errors";
        $result['errors'] = $errors;
    } else {
        $result['success'] = true;
        $result['message'] = "Sync completed successfully. Updated $successCount files.";
    }

    $result['action'] = 'pushed';
    return $result;
}

// Handle the request
try {
    $startTime = microtime(true);

    // If called via HTTP with a custom message
    if ($isHttp &amp;amp;&amp;amp; isset($_GET['message'])) {
        $COMMIT_MESSAGE = $_GET['message'];
    }

    $result = syncToGitHub($GITHUB_TOKEN, $GITHUB_USERNAME, $GITHUB_REPO, $BRANCH, $COMMIT_MESSAGE);
    $result['execution_time'] = round(microtime(true) - $startTime, 2) . ' seconds';

    if ($isHttp) {
        echo json_encode($result, JSON_PRETTY_PRINT);
    } else {
        // CLI output
        if ($result['success']) {
            if ($result['action'] === 'pushed') {
                echo "✅ Sync completed successfully!\n";
                echo "📊 All files: " . $result['all_files'] . "\n";
                echo "📊 Changed files: " . $result['changed_files'] . "\n";
                echo "📊 Skipped files: " . $result['skipped_files'] . "\n";
                echo "⏱️  Execution time: " . $result['execution_time'] . "\n";

                // List files if there are not too many
                if ($result['changed_files'] &amp;gt; 0 &amp;amp;&amp;amp; $result['changed_files'] &amp;lt;= 20) {
                    echo "\n📄 Files synced:\n";
                    foreach ($result['files'] as $file) {
                        echo "  - $file\n";
                    }
                }
            } else {
                echo "✅ No changes to commit. Everything is up to date.\n";
                echo "📊 Files checked: " . $result['all_files'] . "\n";
                echo "⏱️  Execution time: " . $result['execution_time'] . "\n";
            }
        } else {
            echo "❌ Sync completed with errors:\n";
            echo "📊 Successes: " . ($result['changed_files'] - count($result['errors'])) . "\n";
            echo "📊 Errors: " . count($result['errors']) . "\n";
            echo "⏱️  Execution time: " . $result['execution_time'] . "\n";

            foreach ($result['errors'] as $error) {
                echo "❌ $error\n";
            }
        }
    }
} catch (Exception $e) {
    $error = ['success' =&amp;gt; false, 'message' =&amp;gt; 'Exception: ' . $e-&amp;gt;getMessage()];

    if ($isHttp) {
        echo json_encode($error, JSON_PRETTY_PRINT);
    } else {
        echo "❌ Exception: " . $e-&amp;gt;getMessage() . "\n";
    }
}

// If called via HTTP, add some HTML styling
if ($isHttp) {
    echo &amp;lt;&amp;lt;&amp;lt;HTML
    &amp;lt;!DOCTYPE html&amp;gt;
    &amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;title&amp;gt;GitHub Sync Result&amp;lt;/title&amp;gt;
        &amp;lt;style&amp;gt;
            body {
                font-family: Arial, sans-serif;
                margin: 20px;
                background: #f5f5f5;
            }
            .container {
                max-width: 800px;
                margin: 0 auto;
                background: white;
                padding: 20px;
                border-radius: 5px;
                box-shadow: 0 2px 5px rgba(0,0,0,0.1);
            }
            .success {
                color: #2ecc71;
                font-weight: bold;
            }
            .error {
                color: #e74c3c;
                font-weight: bold;
            }
            .info {
                color: #3498db;
                font-weight: bold;
            }
            pre {
                background: #f8f8f8;
                padding: 15px;
                border-radius: 5px;
                overflow: auto;
            }
        &amp;lt;/style&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;div class="container"&amp;gt;
            &amp;lt;h1&amp;gt;GitHub Sync Result&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;Repository: &amp;lt;a href="https://github.com/$GITHUB_USERNAME/$GITHUB_REPO" target="_blank"&amp;gt;$GITHUB_USERNAME/$GITHUB_REPO&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;Scanning: JSON files in root directory and images in /images directory&amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;Commit message: $COMMIT_MESSAGE&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
HTML;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;`&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>git</category>
      <category>automation</category>
    </item>
    <item>
      <title>Making use of the dropzone in your weweb components</title>
      <dc:creator>Tim Geyssens</dc:creator>
      <pubDate>Wed, 26 Jun 2024 08:47:48 +0000</pubDate>
      <link>https://dev.to/timgeyssens/making-use-of-the-dropzone-in-your-weweb-components-536h</link>
      <guid>https://dev.to/timgeyssens/making-use-of-the-dropzone-in-your-weweb-components-536h</guid>
      <description>&lt;p&gt;In this article I'll explain how to make use of the dropzone when building an app on the nocode/lowcode &lt;a href="https://www.weweb.io/" rel="noopener noreferrer"&gt;weweb.io&lt;/a&gt; platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dropzone?
&lt;/h2&gt;

&lt;p&gt;Just to get this clear, the name might suggest that it has to do with file uploads but in this context it is &lt;strong&gt;not&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;A dropzone will enable you to make the &lt;a href="https://docs.weweb.io/components/intro-to-components.html" rel="noopener noreferrer"&gt;components&lt;/a&gt; you create in weweb even &lt;strong&gt;more versatile and dynamic&lt;/strong&gt;. Since by integrating a dropzone into a component it still allows you to add additional items to the component when implementing it in your app, so on the instance of the component (this will get clear in the use case and example). See it as a placeholder you can populate when making use of your component. All to make sure we do not have to repeat our selves by creating a lot of similar items/components.&lt;/p&gt;

&lt;p&gt;If you are not familiar with components make sure to check the  &lt;a href="https://docs.weweb.io/components/intro-to-components.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; on those since we will extend on that topic (components, component &lt;strong&gt;variables&lt;/strong&gt; and component &lt;strong&gt;workflows&lt;/strong&gt; are important concepts to have a grip on before starting with this article).&lt;/p&gt;

&lt;h2&gt;
  
  
  The use case
&lt;/h2&gt;

&lt;p&gt;Let say we have to implement these 3 contact forms on our app. &lt;/p&gt;

&lt;p&gt;Form A consists of a &lt;strong&gt;name/email and message&lt;/strong&gt; (so used as a contact form).&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%2Fp3adjjg9fi96jj7rr9oj.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%2Fp3adjjg9fi96jj7rr9oj.png" alt="form a" width="564" height="780"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Form B consists of a *&lt;em&gt;name/email a department selector and message *&lt;/em&gt;(for contacting a specific department)&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%2F94fzb9cfby5b4w4alll3.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%2F94fzb9cfby5b4w4alll3.png" alt="form b" width="560" height="966"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And form C consists of a &lt;strong&gt;name/email a product name and serial number&lt;/strong&gt; (for a contact relating to a specific product)&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%2F5lrsivepigqb9c39lig0.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%2F5lrsivepigqb9c39lig0.png" alt="form c" width="558" height="1100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All forms need to expose the form data as a json object. Since that will be used down the line.&lt;/p&gt;

&lt;p&gt;You can see there is a lot of overlap between the forms. &lt;/p&gt;

&lt;p&gt;They all have &lt;strong&gt;name/email/message&lt;/strong&gt; and the submit button (highlighted in yellow).&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%2Fah5ls7d0xe0vyqedpu1j.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%2Fah5ls7d0xe0vyqedpu1j.png" alt="overlap" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of creating 3 different forms we can simply create a single form component containing the mutual items.&lt;/p&gt;

&lt;p&gt;And extend the instances of the component if necessary with additional items (in this case extra form inputs).&lt;/p&gt;

&lt;h2&gt;
  
  
  Ground work
&lt;/h2&gt;

&lt;p&gt;Lets first setup the form component with a variable. The goal of the form is that it will expose the form fields as an object. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;{&lt;br&gt;
  "name": "Tim",&lt;br&gt;
  "email": "tim@weweb.io",&lt;br&gt;
  "message": "&amp;lt;p&amp;gt;drop it like it's hot&amp;lt;/p&amp;gt;"&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When implementing the form component we can then send the exposed data to the workflow/service we wish.&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%2Fns2y8vsiu01lwpftntu5.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%2Fns2y8vsiu01lwpftntu5.png" alt="Form component" width="524" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A simple contact form containing the 3 inputs (the inputs have been given a name, so it is easy to keep track when we are binding afterwards).&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%2Fwex6uuv08vw5camlgkg8.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%2Fwex6uuv08vw5camlgkg8.png" alt="Compontent name" width="580" height="208"&gt;&lt;/a&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%2Fis2n1tkflcqvsuzb58px.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%2Fis2n1tkflcqvsuzb58px.png" alt="The rendered result" width="800" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we have to make sure that the component exposes the data. By adding a &lt;strong&gt;variable&lt;/strong&gt; to the component called &lt;strong&gt;"data_to_submit"&lt;/strong&gt; of &lt;strong&gt;type object&lt;/strong&gt; (since this will contain several values) and we will be &lt;strong&gt;exposing this outside&lt;/strong&gt; the component so we can handle the data when the form is in use.&lt;/p&gt;

&lt;p&gt;The variable setup should look like this&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%2Ftcfj2xj6edn0tvd2l0sa.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%2Ftcfj2xj6edn0tvd2l0sa.png" alt="Variable setup" width="638" height="842"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next we need to populate the variable with the values of the inputs.&lt;/p&gt;

&lt;p&gt;Adding a workflow on each input (so the name, email and message input)&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%2Flw409q56v1zttky8i6z5.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%2Flw409q56v1zttky8i6z5.png" alt="workflow" width="596" height="608"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We want to set the &lt;strong&gt;value of a property **of the exposed data to the v&lt;/strong&gt;alue of the corresponding input**.&lt;/p&gt;

&lt;p&gt;So that we end up with this result&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{&lt;br&gt;
  "name": "Tim",&lt;br&gt;
  "email": "tim@weweb.io",&lt;br&gt;
  "message": "&amp;lt;p&amp;gt;drop it like it's hot&amp;lt;/p&amp;gt;"&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The workflow will &lt;strong&gt;trigger on change&lt;/strong&gt; and it will c*&lt;em&gt;hange the data_to_submit variable&lt;/em&gt;*.&lt;/p&gt;

&lt;p&gt;But it will only &lt;strong&gt;partially&lt;/strong&gt; change it (when triggered on change of the** name input** it should only change &lt;strong&gt;the name property&lt;/strong&gt;, when triggered on email only the email property,...)&lt;/p&gt;

&lt;p&gt;Select the data_to_submit variable, chose to &lt;strong&gt;partial update&lt;/strong&gt; and set &lt;strong&gt;the path&lt;/strong&gt; to the &lt;strong&gt;corresponding property&lt;/strong&gt; you wish to update (name for name, email for email,...)&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%2Fkm6akqztkvd4lyydqins.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%2Fkm6akqztkvd4lyydqins.png" alt="Image description" width="594" height="900"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally we can &lt;strong&gt;bind it to the input values&lt;/strong&gt; by selecting the correct form field component variable (aka the name of the input followed by value).&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%2Fhr0w8erpfw1i0o2rny8x.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%2Fhr0w8erpfw1i0o2rny8x.png" alt="Image description" width="800" height="1336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we have a working form component that exposes data containing all the form input values!&lt;/p&gt;

&lt;h3&gt;
  
  
  Component workflow
&lt;/h3&gt;

&lt;p&gt;Of course we need to make sure items added in the dropzone can call a workflow on the form component. So define the following.&lt;/p&gt;

&lt;p&gt;The workflow needs &lt;strong&gt;2 parameters&lt;/strong&gt; (that will be populated from items in the dropzone). The &lt;strong&gt;key and value&lt;/strong&gt; of the property on the data_to_submit we want to fill.&lt;/p&gt;

&lt;p&gt;So add those 2.&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%2Fi748252oi3068bjswado.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%2Fi748252oi3068bjswado.png" alt="Image description" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With those in place we can setup the rest of the workflow.&lt;/p&gt;

&lt;p&gt;Allow &lt;strong&gt;execution from outside&lt;/strong&gt; and from a dropzone. To make sure the workflow is accessible. &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%2Ffvks1nxfzgxgrd80qjan.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%2Ffvks1nxfzgxgrd80qjan.png" alt="Image description" width="604" height="1366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Binding the &lt;strong&gt;path the the key param **and the **value to the value param&lt;/strong&gt; created earlier.&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%2Flifzg54d96396nysevkz.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%2Flifzg54d96396nysevkz.png" alt="Image description" width="800" height="1223"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Hello dropzone
&lt;/h2&gt;

&lt;p&gt;Just add it in the place where you want to be able to add additional form fields when using the form component. &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%2Fiqa40caqepzulhtlj4mc.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%2Fiqa40caqepzulhtlj4mc.png" alt="Image description" width="542" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting the dots
&lt;/h3&gt;

&lt;p&gt;Now we are able to execute the workflow on the form component from items inside the dropzone.&lt;/p&gt;

&lt;p&gt;Not in the component editor but in your instance of the component you can &lt;strong&gt;add extra items&lt;/strong&gt; into the dropzone.&lt;/p&gt;

&lt;p&gt;You can see that the dropzone is visible with a &lt;strong&gt;dotted line&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%2Fzma5vngx0nuuxo8dtigx.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%2Fzma5vngx0nuuxo8dtigx.png" alt="Image description" width="800" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And you can just add what you want, &lt;strong&gt;making the component a lot more flexible and dynamic&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%2Fg0nt0mvzpc0ddlr9bvfk.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%2Fg0nt0mvzpc0ddlr9bvfk.png" alt="Image description" width="536" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'll add a new field with the name &lt;strong&gt;field_from_instance&lt;/strong&gt; and will execute the component workflow on change of the value.&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%2Fno3t4w1ru7kamwg0ytpu.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%2Fno3t4w1ru7kamwg0ytpu.png" alt="Image description" width="610" height="1034"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the key param I just set something static but for the value I &lt;strong&gt;bind it to the event value&lt;/strong&gt;. (so since this is triggered on change of an input the event value will be the value of the input).&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%2Fgurqyy32ky16bdlrp2qw.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%2Fgurqyy32ky16bdlrp2qw.png" alt="Image description" width="800" height="1050"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you now check the variables in the debugger you will notice that the data_to_submit will also update with the data from the dropped in input.&lt;/p&gt;

&lt;p&gt;Of course** keys need to be unique**. So a unique key per input.&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%2Faqs466xohqot9xa8decp.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%2Faqs466xohqot9xa8decp.png" alt="Image description" width="542" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that is it, a base contact form component that features a dropzone to allow further customisation when using the component in your app.&lt;/p&gt;

</description>
      <category>weweb</category>
      <category>nocode</category>
      <category>lowcode</category>
    </item>
    <item>
      <title>Umbraco Forms Fieldtype with custom client and serverside validation</title>
      <dc:creator>Tim Geyssens</dc:creator>
      <pubDate>Fri, 25 Nov 2022 09:12:14 +0000</pubDate>
      <link>https://dev.to/timgeyssens/umbraco-forms-fieldtype-with-custom-client-and-serverside-validation-1a7a</link>
      <guid>https://dev.to/timgeyssens/umbraco-forms-fieldtype-with-custom-client-and-serverside-validation-1a7a</guid>
      <description>&lt;p&gt;Umbraco Forms allows you to add custom fieldtypes, I won´t go into detail on how to do that since you can find detailed docs over at &lt;a href="https://our.umbraco.com/documentation/Add-ons/UmbracoForms/Developer/Extending/Adding-a-Fieldtype" rel="noopener noreferrer"&gt;our umbraco&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see it is also easy to add server side validation on a fieldtype level.&lt;/p&gt;

&lt;p&gt;Let´s say you want have a phone number field and validate the number agains on api (to make sure it exists)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; public override IEnumerable&amp;lt;string&amp;gt; ValidateField(Form form, Field field, IEnumerable&amp;lt;object&amp;gt; postedValues, HttpContext context, IPlaceholderParsingService placeholderParsingService)
        {
            var returnStrings = new List&amp;lt;string&amp;gt;();

            var service = new NumverifyService();

            if (postedValues.Any() &amp;amp;&amp;amp; !service.Verify(postedValues.First().ToString()))
            {
                returnStrings.Add(ValidPhoneNumber);
            }

            returnStrings.AddRange(base.ValidateField(form, field, postedValues, context, placeholderParsingService));

            return returnStrings;
        }

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

&lt;/div&gt;



&lt;p&gt;So that is the server side validation sorted.&lt;/p&gt;

&lt;p&gt;For client side I´ll call the same NumverifyService from a api controller&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    public class FormApiController : UmbracoApiController
    {
        private readonly NumverifyService _service;

        public FormApiController()
        {
            _service = new NumverifyService();
        }


        [HttpGet]
        public bool Check()
        {

            return _service.Verify(HttpContext.Current.Request.Params[0]);
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once thing to notice here is that I don´t use a param in the check method since form fields are named based on a guid...not on the alias of the form field. So I´ll just fetch the first request param.&lt;/p&gt;

&lt;p&gt;So now how do you hook this up with the custom fieldtype? And do remove validation (Remote Validation is a technique that uses client side script to validate user input on the server without posting the entire form)&lt;/p&gt;

&lt;p&gt;Since by default forms is using unobtrusive validation it can be handled by adding these attributes to the element.  in the form field razor view&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;       data-val-remote="Not a valid phone number"
       data-val-remote-additionalfields="*.@Model.Name"
       data-val-remote-url="/Umbraco/Api/FormApi/Check/"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the error message, the field to send to the remote url and the url of the api method.&lt;/p&gt;

&lt;p&gt;So no need for additional js...&lt;/p&gt;

</description>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>A simple send umbraco page as test mail content app</title>
      <dc:creator>Tim Geyssens</dc:creator>
      <pubDate>Mon, 10 Jan 2022 09:23:02 +0000</pubDate>
      <link>https://dev.to/timgeyssens/a-simple-send-umbraco-page-as-test-mail-content-app-2lmc</link>
      <guid>https://dev.to/timgeyssens/a-simple-send-umbraco-page-as-test-mail-content-app-2lmc</guid>
      <description>&lt;p&gt;&lt;a href="https://our.umbraco.com/documentation/extending/Content-Apps/" rel="noopener noreferrer"&gt;Content Apps&lt;/a&gt; are companions to the editing experience when working with content or media in the Umbraco backoffice.&lt;/p&gt;

&lt;p&gt;So unlike prop editors they don't store any data against the current document.&lt;/p&gt;

&lt;p&gt;A usecase I recently encountered was the ability to generate newsletters from Umbraco and send them with a third party tool (so import the outputted html with a mailing service like mailchimp).&lt;/p&gt;

&lt;p&gt;But the editors also wanted the ability to first send a single test mail from within Umbraco before migrating the markup.&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%2Fiqxencjda5fx2alj6zot.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%2Fiqxencjda5fx2alj6zot.png" alt="Send test email"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The solution consists of an API Controller that fetches the rendered output and sends that as an email.&lt;/p&gt;

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

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Core.Logging;
using Umbraco.Web.WebApi;
using Umbraco.Web;
using Umbraco.Core.Models.PublishedContent;
using System.Web.Http;

namespace Acme.Core.Controllers.API
{
    public class NewsLetterController : UmbracoAuthorizedApiController
    {
        private readonly ILogger _logger;
        private readonly IProfilingLogger _profilingLogger;
        public NewsLetterController(ILogger logger, IProfilingLogger profilingLogger)
        {

            this._logger = logger;
            this._profilingLogger = profilingLogger;
        }

        [HttpGet]
        public bool SendTestMail(int newsletterId, string email)
        {
            var doc = this.Umbraco.Content(newsletterId);

            if (doc != null)
            {
                string markup = string.Empty;

                if (this.GetHtmlByUrl(doc.Url(mode: UrlMode.Absolute), out markup))
                {
                    var message = new MailMessage("noreply@acme.com", email);
                    message.Subject = doc.Name;
                    message.Body = markup;
                    message.IsBodyHtml = true;
                    var client = new SmtpClient();
                    client.Send(message);

                    return true;
                }
            }

            return false;
        }

        private bool GetHtmlByUrl(string url, out string fullHtml)
        {
            using (this._profilingLogger.DebugDuration&amp;lt;NewsLetterController&amp;gt;("GetHtmlByUrl(" + url + ")", "GetHtmlByUrl(" + url + ") done"))
            {

                if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri))
                {
                    fullHtml = "";
                    return false;
                }

                try
                {
                    var httpTimeout = 1000;

                    var cookieDictionary = new Dictionary&amp;lt;string, string&amp;gt;();
                    var webRequest = (HttpWebRequest)WebRequest.Create(uri);
                    httpTimeout *= 1000;
                    webRequest.Timeout = httpTimeout;
                    webRequest.UserAgent = "NewsLetterController";

                    if (cookieDictionary != null &amp;amp;&amp;amp; cookieDictionary.Count &amp;gt; 0)
                    {
                        var container = new CookieContainer();
                        var domain = webRequest.Address.DnsSafeHost;
                        foreach (var cookie in cookieDictionary)
                        {
                            container.Add(new Cookie(cookie.Key, cookie.Value, "/", domain));
                        }

                        webRequest.CookieContainer = container;
                    }

                    var webResponse = (HttpWebResponse)webRequest.GetResponse();
                    using (var sr = new StreamReader(webResponse.GetResponseStream()))
                    {
                        fullHtml = sr.ReadToEnd();
                    }

                    return true;
                }
                catch (WebException ex)
                {
                    this._logger.Error&amp;lt;NewsLetterController&amp;gt;(ex, $"Error in NewsLetterController retrieval. Url: {url}");
                    fullHtml = string.Empty;
                }

                return false;
            }
        }
    }
}



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

&lt;/div&gt;

&lt;p&gt;And the actual content app, that outputs a text input and a submit button.&lt;/p&gt;

&lt;p&gt;View&lt;/p&gt;

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

&amp;lt;div ng-controller="Custom.SendPageAsMail as vm"&amp;gt;
  &amp;lt;umb-box&amp;gt;
    &amp;lt;umb-box-header title="Send test mail"&amp;gt;&amp;lt;/umb-box-header&amp;gt;
    &amp;lt;umb-box-content&amp;gt;
      &amp;lt;input type="text" ng-model="vm.emailAddress" class="umb-property-editor umb-textstring textstring" required no-dirty-check /&amp;gt;
      &amp;lt;a class="control__button btn"&amp;gt;
        &amp;lt;i class="icon icon-message" ng-click="sendTestMail()"&amp;gt;Send&amp;lt;/i&amp;gt;
      &amp;lt;/a&amp;gt;
    &amp;lt;/umb-box-content&amp;gt;
  &amp;lt;/umb-box&amp;gt;
&amp;lt;/div&amp;gt;



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

&lt;/div&gt;

&lt;p&gt;Controller (also populates the input with current user email)&lt;/p&gt;

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

angular.module("umbraco")
    .controller("Custom.SendPageAsMail", function ($scope, editorState, $http, userService) {
        var vm = this;
        vm.CurrentNodeId = editorState.current.id;
        vm.emailAddress = "";

        function _sendTestMail() {

            $http.get('/Umbraco/backoffice/Api/NewsLetter/SendTestMail?newsletterId=' + vm.CurrentNodeId + '&amp;amp;email=' + vm.emailAddress).then(function (response) {

            });


        }

        function _init() {

            $scope.sendTestMail = _sendTestMail;

            userService.getCurrentUser().then(function (user) {
                vm.emailAddress = user.email;
            });
        }

        _init();



});



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

&lt;/div&gt;

&lt;p&gt;Package manifest&lt;/p&gt;

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

{

    "contentApps": [
    {
        "name": "Send testmail", 
        "alias": "sendpageasmail", 
        "weight": 0, 
        "icon": "icon-message",
        "view": "~/App_Plugins/SendPageAsMail/SendPageAsMail.html",
        "show": [

            "+content/newsLetter",
            "+contentType/newsLetter"
        ]

    }
    ],

    "javascript": [
        "~/App_Plugins/SendPageAsMail/SendPageAsMail.controller.js"
    ]
}



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

&lt;/div&gt;

</description>
      <category>umbraco</category>
      <category>umbracov8</category>
    </item>
    <item>
      <title>A Flexible Umbraco Embed Provider for url to iframe stuff</title>
      <dc:creator>Tim Geyssens</dc:creator>
      <pubDate>Thu, 18 Nov 2021 13:05:05 +0000</pubDate>
      <link>https://dev.to/timgeyssens/a-flexible-umbraco-embed-provider-for-url-to-iframe-stuff-a5g</link>
      <guid>https://dev.to/timgeyssens/a-flexible-umbraco-embed-provider-for-url-to-iframe-stuff-a5g</guid>
      <description>&lt;p&gt;A feature that I introduced in Umbraco way back in &lt;a href="http://www.nibble.be/?p=196"&gt;2012&lt;/a&gt; (damn I'm getting old) is an easy way to embed third party media by simply providing the url. Behind the scenes that takes advantage of the &lt;a href="https://oembed.com/"&gt;oembed format&lt;/a&gt; but of course not all sites that offer embeddable content have an api endpoint ... sometimes it's also just going from url to an iframe without extra stuff... but you still want to give editors the easy "url only" way of embedding.&lt;/p&gt;

&lt;p&gt;So for a recent project I extended the default Umbraco behaviour with a fresh &lt;a href="https://our.umbraco.com/Documentation/Extending/Embedded-Media-Provider/index-v8"&gt;Embed Provider&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    public class UrlToIframeEmbedProvider: EmbedProviderBase
    {
        // no ApiEndpoint!
        public override string ApiEndpoint =&amp;gt; String.Empty;
        public override string[] UrlSchemeRegex
        {
            get
            {
                var urls = new List&amp;lt;string&amp;gt;();
                var doc = new XmlDocument();
                doc.Load(System.Web.Hosting.HostingEnvironment.MapPath("~/config/UrlToIframeEmbedProvider.xml"));

                if (doc != null)
                {
                    foreach(XmlNode node in doc.SelectNodes("//embed"))
                    {
                        urls.Add(node.Attributes["regex"].Value);
                    }
                }

                return urls.ToArray();
            }

        }

        public override Dictionary&amp;lt;string, string&amp;gt; RequestParams =&amp;gt; new Dictionary&amp;lt;string, string&amp;gt;();

        public override string GetMarkup(string url, int maxWidth, int maxHeight)
        {
            var markup = string.Empty;
            var doc = new XmlDocument();
            doc.Load(System.Web.Hosting.HostingEnvironment.MapPath("~/config/UrlToIframeEmbedProvider.xml"));

            if (doc != null)
            {
                foreach (XmlNode node in doc.SelectNodes("//embed"))
                {
                    var m = Regex.Match(url, node.Attributes["regex"].Value, RegexOptions.IgnoreCase);
                    if (m.Success)
                    {
                        markup = node.InnerText;
                        break;
                    }
                }
            }

            if (!string.IsNullOrEmpty(markup))
            {
                return string.Format(markup, url, maxWidth,maxHeight);
            }else
            {
                return markup;
            }
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now simply populate the config file with different url regexes and the embed codes using the string.format placeholders &lt;/p&gt;

&lt;p&gt;{0}: the url&lt;br&gt;
{1}: the width&lt;br&gt;
{2}: the height&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?xml version="1.0" encoding="utf-8" ?&amp;gt;
&amp;lt;customEmbeds&amp;gt;
  &amp;lt;embed regex="site\.com/*"&amp;gt;
    &amp;lt;![CDATA[&amp;lt;iframe width="{1}" height="{2}" style="width: {1}px; height: {2}px; border: none; overflow: hidden;"
        src="{0}" webkitAllowFullScreen="true" mozallowfullscreen="true" allowFullScreen="true"
        frameborder="0" scrolling="no" allowtransparency="true" &amp;gt;&amp;lt;/iframe&amp;gt;  ]]&amp;gt;
  &amp;lt;/embed&amp;gt;
&amp;lt;/customEmbeds&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>umbraco</category>
    </item>
    <item>
      <title>Umbraco HQ AMA session, get your questions in!</title>
      <dc:creator>Tim Geyssens</dc:creator>
      <pubDate>Tue, 02 Feb 2021 11:20:52 +0000</pubDate>
      <link>https://dev.to/timgeyssens/umbraco-hq-ama-session-get-your-questions-in-2mop</link>
      <guid>https://dev.to/timgeyssens/umbraco-hq-ama-session-get-your-questions-in-2mop</guid>
      <description>&lt;p&gt;During some recent events HQ asked for questions from the community but they never got answered, so I launched an attempt at having an open AMA session.&lt;/p&gt;

&lt;p&gt;Will be hosted by the great folks of &lt;a href="[https://www.youtube.com/channel/UCF_Ene5-58a3Z55aw8O6Djg"&gt;Umbracoffee&lt;/a&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%2Fi%2F0pybk0eu2sduyj64o1kq.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%2Fi%2F0pybk0eu2sduyj64o1kq.png" alt="Alt Text" width="576" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Get your question(s) in -&amp;gt; &lt;a href="https://docs.google.com/forms/d/e/1FAIpQLScGOrmEkigivmqD3AASepvbHURpYLgpPSfyvPZQFStUI8ZxWg/viewform"&gt;google form&lt;/a&gt;&lt;br&gt;
View the current questions -&amp;gt; &lt;a href="https://docs.google.com/spreadsheets/d/1aJDxDR-x71A5YjiOVu2fNmE1Xj1Fy1jrY_Yk-XWjSMI/edit#gid=1439331290"&gt;google sheet&lt;/a&gt;&lt;/p&gt;

</description>
      <category>umbraco</category>
    </item>
    <item>
      <title>The open source sponsorship experiment</title>
      <dc:creator>Tim Geyssens</dc:creator>
      <pubDate>Fri, 18 Dec 2020 11:47:07 +0000</pubDate>
      <link>https://dev.to/timgeyssens/the-open-source-sponsorship-experiment-4iaa</link>
      <guid>https://dev.to/timgeyssens/the-open-source-sponsorship-experiment-4iaa</guid>
      <description>&lt;p&gt;As you may have noticed. You can now also sponsor an open source contributor as a company (a.k.a get an invoice)...&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%2Fi%2Fsfi0t0j01wdiaair3rlr.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%2Fi%2Fsfi0t0j01wdiaair3rlr.png" alt="Alt Text" width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the full details &lt;a href="https://github.com/sponsors"&gt;read the post over at github&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;It's no secret that finding a business model with open source is a hard nut to crack... especially when it is a smaller niche product/addon. &lt;/p&gt;

&lt;p&gt;So what happens a lot is that open source projects simply turn into abandonware (creator loses interest). As a dev with the code still available you could spend time into adding/fixing what you want of course... but plenty of folks around that just use it without the tech knowledge required to update. &lt;/p&gt;

&lt;p&gt;Years ago I made a package that allows you to prevent conflicting edits in Umbraco v7. &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%2Fi%2F9mx6m20ljg343pxxdh3n.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%2Fi%2F9mx6m20ljg343pxxdh3n.png" alt="Conflicting edits" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Short video here:&lt;br&gt;
&lt;a href="https://screencast-o-matic.com/watch/co6Vjvf8Lc"&gt;https://screencast-o-matic.com/watch/co6Vjvf8Lc&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now there have been a couple of folks requesting a v8 version... &lt;/p&gt;

&lt;p&gt;As an experiment I now want to see if I can get $100 in sponsorship, once I hit that target I'll make the v8 version.&lt;/p&gt;

&lt;p&gt;You can find my sponsor page over at &lt;a href="https://github.com/sponsors/TimGeyssens"&gt;https://github.com/sponsors/TimGeyssens&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And different tiers:&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%2Fi%2Ft0u4rekebgkn796lnjem.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%2Fi%2Ft0u4rekebgkn796lnjem.png" alt="Alt Text" width="350" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So 2 companies in the $50 tier or 20 in the $5 tier (or another combo) would work! I don't expect individual personal dev contributions. Compared to doing the dev inhouse this is peanuts.&lt;/p&gt;

&lt;p&gt;Let's see how this works out.&lt;/p&gt;

</description>
      <category>umbraco</category>
      <category>opensource</category>
      <category>funding</category>
      <category>github</category>
    </item>
    <item>
      <title>Setting up an Umbraco Baseline-ish flow with Azure Devops</title>
      <dc:creator>Tim Geyssens</dc:creator>
      <pubDate>Tue, 08 Dec 2020 21:39:23 +0000</pubDate>
      <link>https://dev.to/timgeyssens/setting-up-an-umbraco-baseline-ish-flow-with-azure-devops-3319</link>
      <guid>https://dev.to/timgeyssens/setting-up-an-umbraco-baseline-ish-flow-with-azure-devops-3319</guid>
      <description>&lt;p&gt;In my previous post I outlined &lt;a href="https://dev.to/timgeyssens/is-umbraco-cloud-a-good-fit-for-your-umbraco-project-34dh"&gt;my experiences with Umbraco Cloud&lt;/a&gt;. It boils down to the fact that if you are a team of devs and follow a gitflow or similar release management workflow Cloud just isn't an option. &lt;/p&gt;

&lt;p&gt;So I was investigating alternatives that allow a release management workflow... but I still want a baseline and auto upgrades.&lt;/p&gt;

&lt;h1&gt;
  
  
  Azure devops
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;DevOps is the union of people, process, and products to enable continuous delivery of value to our end users.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Devops consists of a lot more but in this post I'll outline how I'll use CI &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Continuous Integration (CI) is the process of automating the build and testing of code every time a team member commits changes to version control.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and CD&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Continuous Delivery (CD) is the process to build, test, configure and deploy from a build to a production environment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Of course when developing Umbraco sites you keep your code in sourcecontrol adding CI into the mix we can make sure the code "builds" when new changes are commited. &lt;/p&gt;

&lt;h2&gt;
  
  
  Staging site
&lt;/h2&gt;

&lt;p&gt;For simplicity I'll keep everyting in &lt;a href="https://azure.microsoft.com/en-us/services/devops/" rel="noopener noreferrer"&gt;Azure devops&lt;/a&gt;. But you can also have this in for example github.&lt;/p&gt;

&lt;p&gt;Say I have a Umbraco project with a Core + Web + Test project.&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%2Fi%2Fbqsahj7yslyw5n5agzq0.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%2Fi%2Fbqsahj7yslyw5n5agzq0.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can then first setup a pipeline that builds and deploys the test version (from the release branch). So new features get developed on feature branches, when they need to go to the client for review you create a pull request or merge the feature branch with release. &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%2Fi%2Fp4h2mer1cxctl5wn5vhm.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%2Fi%2Fp4h2mer1cxctl5wn5vhm.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Setting this up is pretty simple..just add a new pipeline (and I tend to pick the classic visual editor)&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%2Fi%2Fcf2398apuumxtktu4pqz.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%2Fi%2Fcf2398apuumxtktu4pqz.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next you need to select the source... when picking azure you just need to select from dropdowns (which project/repo/branch), easy!&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%2Fi%2Fx4m6mrlq1ukkhte4pn5h.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%2Fi%2Fx4m6mrlq1ukkhte4pn5h.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After that you can select a pipeline template (I pick the azure web app for asp.net, since Umbraco is a asp.net web app and I want to deploy to azure)..&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%2Fi%2Fyyazpwnzz78dfd0cr46m.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%2Fi%2Fyyazpwnzz78dfd0cr46m.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Picking that template most stuff is already in place.&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%2Fi%2Ftgw97y3tjzojqfdcmlhm.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%2Fi%2Ftgw97y3tjzojqfdcmlhm.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The only step that needs attention is selecting the azure subscription and App service you want to deploy to... (so of course you need an  azure subscription with an existing app service).&lt;/p&gt;

&lt;p&gt;Once all that is selected you now have a working CI, CD process for a test environment. So codes get's pushed to the release branch... that triggers a build and deploy...&lt;/p&gt;

&lt;p&gt;One small side note, best is to enable xml transformation on the Azure App Service deploy task, that way you can have  *.Release.config  file transforms (like for settings that are different between your dev env and the actual test env). Stuff like conn strings can be also set that way but also on devops or in azure... (depending on how you want it configured). Plus of not having it in sourcecontrol is that there is an extra level of security (sensitive settings shouldn't go in source control).&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%2Fi%2Fufld7ec1q72eo5ygp1iu.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%2Fi%2Fufld7ec1q72eo5ygp1iu.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Live sites
&lt;/h2&gt;

&lt;p&gt;Of course now we have a staging version of the baseline site... but I also want child websites using that base...&lt;/p&gt;

&lt;p&gt;So adding a new pipeline that will build an artifact (webdeploy package) from the main branch. Following the same steps as before but removing some tasks and adding 1 new one&lt;/p&gt;

&lt;p&gt;This will now run when new code is commited to the main branch... each time a new artifact will be generated.&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%2Fi%2Fjuiwybfcju5t8vm2dpux.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%2Fi%2Fjuiwybfcju5t8vm2dpux.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Important here is the Artifact name (we need this name later) and also the File or directory path, for this I used an available variable $(System.DefaultWorkingDirectory)\ (not sure if that is the correct way but it works!)&lt;/p&gt;

&lt;p&gt;Next is setting up the release pipeline, so in the releases part of pipelines just add a new one (this should get you started with an artifact and stage)&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%2Fi%2Fp880b1wx7yqvh47psq00.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%2Fi%2Fp880b1wx7yqvh47psq00.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The artifact then points to the latest pipeline (where we create the artifact), we also need to specify the artifact name here. (needs to match with what is on the pipeline)&lt;/p&gt;

&lt;p&gt;Finaly a task per child is setup... this just uses a Azure App Service deploy task that points to the correct app service (again you need to have the app service created on your portal)&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%2Fi%2Fqclz04g06dzc3uuyr5fo.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%2Fi%2Fqclz04g06dzc3uuyr5fo.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again stuff like conn string shoudln't be set in the web.config in source control but you can set these in devops or in the azure portal.&lt;/p&gt;

&lt;p&gt;So now when the build of the artifact is finished the deploy will happen to my 2 child sites using the same base...&lt;/p&gt;

&lt;h1&gt;
  
  
  Basics
&lt;/h1&gt;

&lt;p&gt;Of course this outlines the basics... I also have &lt;a href="https://github.com/KevinJump/uSync8" rel="noopener noreferrer"&gt;uSync&lt;/a&gt; in the mix that handles the version control of a lot of umbraco stuff (like doctypes,datatypes,...) and auto imports these on the child sites...&lt;br&gt;
Then with &lt;a href="https://our.umbraco.com/packages/website-utilities/ourumbracosilentupgrade/" rel="noopener noreferrer"&gt;silent upgrade&lt;/a&gt; also a amazing free addon to Umbraco from &lt;a href="https://jumoo.co.uk/" rel="noopener noreferrer"&gt;Kevin Jump&lt;/a&gt; added we can first test an upgrade localy and if that succeeds roll it out as part of a release ... just add the nuget package and set  (possible with a config transform)&lt;/p&gt;

&lt;h1&gt;
  
  
  Further
&lt;/h1&gt;

&lt;p&gt;Now I have 1 codebase for all the sites... with a &lt;a href="https://dev.to/timgeyssens/setting-up-an-umbraco-site-theme-picker-including-a-context-aware-approved-color-picker-for-multisite-environments-4ila"&gt;theme picker&lt;/a&gt; this could work in having a different look and feel for each child site... so a content defined theme... if you do want a different code base that is also possible. New git repo, add the base as an origin and you can pull/push from the base... of course you then need to setup a new build/release pipeline for that case...&lt;/p&gt;

&lt;p&gt;You could also have staging sites for all the child one... just create a release artifact and add a new release pipeline...deploying to staging locations...&lt;/p&gt;

&lt;p&gt;Of course blog storage is also possible, again, each child would have it's own config...&lt;/p&gt;

&lt;p&gt;Key is to develop with the concept of a base site in mind, so make everything as flexible as possible, might even introduce some interfaces so you can switch different bits depending on config. For example an applicant tracking system, child a might be using umbraco content, child b an external api... making that swappable still allows a single code base...&lt;/p&gt;

&lt;p&gt;If you want to fetch content from live to develop/test against you can add &lt;a href="https://jumoo.co.uk/usync/complete/" rel="noopener noreferrer"&gt;uSync complete&lt;/a&gt; that contains &lt;a href="https://jumoo.co.uk/usync/publisher/" rel="noopener noreferrer"&gt;uSync publisher&lt;/a&gt;. Or just download the db and run on that locally...&lt;/p&gt;

&lt;h1&gt;
  
  
  Pros
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Plug and play in your existing dev flow (doesn't matter where you have your sourcecode, github, bitbucket,...)&lt;/li&gt;
&lt;li&gt;Superbe logging of each step, if something goes wrong you have detailed logs available in devops... you can see them as the build/deploy is in progress&lt;/li&gt;
&lt;li&gt;Possible to run unit tests and don't deploy if these fail&lt;/li&gt;
&lt;li&gt;Not a single point of failure, say the hosting goes down you can setup a deploy to a new endpoint... devops goes down, use a publish profile directly from vs&lt;/li&gt;
&lt;li&gt;No sudden price increase when you hit certain usage limits (pageviews/node/domain/bandwidth/media) it just scales depending on what you use.&lt;/li&gt;
&lt;li&gt;You can take advantage of the already existing task in devops to further finetune the CI CD , like a &lt;a href="https://marketplace.visualstudio.com/items?itemName=luuksommers.website-warmup" rel="noopener noreferrer"&gt;site warmup step&lt;/a&gt;, thanks &lt;a href="https://twitter.com/sjorspa" rel="noopener noreferrer"&gt;Sjors&lt;/a&gt;!&lt;/li&gt;
&lt;li&gt;Forum support included ;) &lt;/li&gt;
&lt;li&gt;Kevin provides stellar support (even for his free addons)&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Cons
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;You need some devops/azure knowledge (but plenty of documentation out there, since it is all coupled to your microsoft account it's super easy to select the correct stuff...)&lt;/li&gt;
&lt;li&gt;Setting up a new child takes some manual work (about 30 mins) create app service, db, update the release pipeline...(you could look into automating this if you have the knowledge/time)&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Overall
&lt;/h1&gt;

&lt;p&gt;Unless Umbraco Cloud introduces a significant price drop and would allow for a release management workflow I wouldn't look back twice. Azure Devops is a really mature product that makes the whole continuous Integration/delivery a breeze...&lt;/p&gt;

&lt;p&gt;From what I hear other folks are also using &lt;a href="https://octopus.com/" rel="noopener noreferrer"&gt;Octopus Deploy&lt;/a&gt; and &lt;a href="https://github.com/features/actions" rel="noopener noreferrer"&gt;Github actions&lt;/a&gt; to get a similar workflow. &lt;/p&gt;

</description>
      <category>umbraco</category>
      <category>devops</category>
      <category>azure</category>
    </item>
    <item>
      <title>Is Umbraco Cloud a good fit for your Umbraco project?</title>
      <dc:creator>Tim Geyssens</dc:creator>
      <pubDate>Wed, 25 Nov 2020 12:39:08 +0000</pubDate>
      <link>https://dev.to/timgeyssens/is-umbraco-cloud-a-good-fit-for-your-umbraco-project-34dh</link>
      <guid>https://dev.to/timgeyssens/is-umbraco-cloud-a-good-fit-for-your-umbraco-project-34dh</guid>
      <description>&lt;p&gt;I've been around a long time in the Umbraco dev community, one of the struggles in the early years was having a decent &lt;a href="https://en.wikipedia.org/wiki/Development,_testing,_acceptance_and_production"&gt;dtap&lt;/a&gt; flow&lt;/p&gt;

&lt;p&gt;Umbraco is a db driven asp.net project. Some things are stored on filesystem like js/css/templates but others are in the db. Also in v2-7.x everything in the db only had a unique integer id. Once you then had multiple environments and you do some further development these id's won't match... &lt;br&gt;
Live gets new content, dev gets new doctypes... so the id of these new doctypes wouldn't be the same accross environments. Often deploying stuff between environments also consisted of doing manual changes to keep everything in sync...so not exactly an easy/automated process. And also not possible to put all bits in sourcecontrol...&lt;/p&gt;

&lt;h2&gt;
  
  
  Courier
&lt;/h2&gt;

&lt;p&gt;Enter &lt;a href="https://umbraco.com/products/umbraco-courier/"&gt;Umbraco Courier&lt;/a&gt; at some point HQ released Courier, a commercial addon that allows you to transfer changes between environments...this even allowed revisions/dependencies checking... but as you know Umbraco is highly customizable and you can add new sections/prop editors/data types... and those can work with content/media/other ids in a nested structure... So to make everything sync at the push of a button is a super complex thing...Courier worked good with pretty standard setups but it had/has it pains...&lt;/p&gt;

&lt;h2&gt;
  
  
  uSync
&lt;/h2&gt;

&lt;p&gt;Enter &lt;a href="https://jumoo.co.uk/usync/"&gt;uSync&lt;/a&gt; since Courier wasn't cutting it for all cases a community member released uSync. This serializes the db stuff to disk (so you can have it in sourcecontrol) and can import those disk items back into Umbraco/db on out of sync environment... I'm not sure about all the details it had initialy but I don't think it was able to serialize everything...but these days it does... even content...&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloud
&lt;/h2&gt;

&lt;p&gt;Enter &lt;a href="https://umbraco.com/products/umbraco-cloud/"&gt;Cloud&lt;/a&gt; of course running umbraco is more then just source control so HQ released Cloud, which allows you to spin up a new Umbraco instance in a couple of minutes ... add different environments and sync between these. Also has auto upgrades, sourcecontrol of course and other features like Baselines... so it is more then just hosting...At that time the pricing model was also simpel...pay per env.. no limits on bandwidth/nodes/storage/domains&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial finding
&lt;/h3&gt;

&lt;p&gt;When I first started exploring Cloud it looked great, for a client that wanted a decent dtap I tested the different options at that time (4-5 years ago) and made the recommendation to go on cloud with a big multisite project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dtap
&lt;/h3&gt;

&lt;p&gt;THe development of the project was going ok.... the developers used the grid with doctype grid editor... so far so good. The initial sites then whent live ... and a new env was added to do further dev.&lt;/p&gt;

&lt;p&gt;Of course since there wasn't a recommended approach for building the backend, grid and doctype grid editor was in place so id's of items could be nested pretty deep (in json stored in the db).&lt;/p&gt;

&lt;p&gt;Unfortunately during these early Cloud months that caused a lot of pain...deploys would fail, site would go down... pushing that deploy button was always a super stressy moment. And if it was down you would need to contact support to get it back...Combined with some cloud outages the decision was made to move the site to Azure... currenlty those have been running smoothly on azure with USync combo... Also there wasn't a really good way of keeping a main repo (out of cloud) in sync and pushing from a CI way to cloud.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further attemps
&lt;/h3&gt;

&lt;p&gt;In the following years I did a couple of other deploys to Cloud, since then the platform matured and 1 item that got added was the uid (so instead of having a different int id accross envs, items now had a single unique umbraco uid that was equal on all). This worked ok in a single developer setup...but of course sticking with Vanilla Umbraco as much as possible, since adding third party bits in some cases also needs extra work to make it work with Deploy/Cloud... + the main repo was also the Cloud one.&lt;/p&gt;

&lt;p&gt;Again some cloud outages and bugs that got in due to auto upgrades was not making it a 100% smooth operation. Combined with the new limits on pageviews/content nodes/domain names/bandwidth/storage... the azure move was done again. Since that scales (from a price point of view) in a more fluent way then a sudden increase from 40$ to 210$ a month.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recent evaluation
&lt;/h3&gt;

&lt;p&gt;In the past months I also checked the state of Cloud. It isn't a secret that in the past month 4 big outages happened that had an effect on all sites.. you can check the history on &lt;a href="https://status.umbraco.io/"&gt;https://status.umbraco.io/&lt;/a&gt;. And the &lt;a href="https://umbraco.com/blog/umbraco-cloud-incident-1311-14112020/"&gt;post on the official blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also there are posts that cover how to &lt;a href="https://moriyama.co.uk/about-us/news/planning-for-site-availability-during-umbraco-cloud-outages/"&gt;prepare for cloud outages&lt;/a&gt; that are still valid today... &lt;/p&gt;

&lt;p&gt;One of the plans was to use baselines in order to setup different sites. But it appears that this isn't as fluent as I imagined it would be. With multiple devs a lot of collision errors happen: &lt;a href="https://our.umbraco.com/documentation/Umbraco-Cloud/Troubleshooting/Deployments/Structure-Error/"&gt;https://our.umbraco.com/documentation/Umbraco-Cloud/Troubleshooting/Deployments/Structure-Error/&lt;/a&gt; there is even a &lt;a href="https://www.youtube.com/playlist?list=PLG_nqaT-rbpzgBQkZtRrdzIpeFbRNvO0E"&gt;video series on youtube&lt;/a&gt; describing on how to handle this. As you see this needs powertools (kudu and powershell) so the complexity that is supposed to hide is still needed...Also if you need support in getting these fixed you need to have the correct plan...&lt;/p&gt;

&lt;p&gt;Also there is no way to do feature branches, have these on staging for a client to test and then deploy the ones you wish to live... it's all or nothing... So if you need to deploy a hotfix... you can't go trough staging to deploy to live since that would deploy everything... some details on that here on the &lt;a href="https://our.umbraco.com/forum/using-umbraco-and-getting-started//104257-umbraco-cloud-and-gitflow-plus-baselines"&gt;forum&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Verdict
&lt;/h3&gt;

&lt;p&gt;If you are a solo dev and the client has budget to spend on Cloud (doesn't worry about the price increase) it can make sense if you don't want to setup your own env on azure or similar. If the Cloud platform becomes stable that is.&lt;/p&gt;

&lt;p&gt;If you are a team of devs, have a bit of devops knowledge, want more control and have gitflow or similar approach. Use Azure devops and add uSync in the mix. No need for the extra layer...&lt;/p&gt;

&lt;p&gt;Also be aware that currently Umbraco Cloud is run on VMs... Like seen in the last outages if something goes wrong this can/will affect a lot of sites.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wishlist
&lt;/h3&gt;

&lt;p&gt;What I would like to see is an Umbraco Devops... that you can just fit in your existing flow to get a decent DTAP. Imagine you signup to a new umbraco.io project, you just point it to your existing repo/branch...(from github for example), then you setup the build with optional tests... could be that cloud has default tests for core/heavily used packages... you could also write/record your own. Once the build and tests are succesfull you can setup where you want to deploy the site. To Azure, a vm, a dedicated server, ftp,...Then Deploy could be used to restore data between backends... (pulling live content to dev to continue dev on a new feature)&lt;/p&gt;

&lt;p&gt;Of course this is all based on my experiences with the platform over the past years...feel free to share your thoughts or tips on how to get it smooth...&lt;/p&gt;

</description>
      <category>umbraco</category>
      <category>review</category>
      <category>saas</category>
    </item>
    <item>
      <title>UI-O-Matic 3.1 for Umbraco the Hacktoberfest 2020 edition is out!</title>
      <dc:creator>Tim Geyssens</dc:creator>
      <pubDate>Wed, 25 Nov 2020 08:46:59 +0000</pubDate>
      <link>https://dev.to/timgeyssens/ui-o-matic-3-1-for-umbraco-the-hacktoberfest-2020-edition-is-out-1i3d</link>
      <guid>https://dev.to/timgeyssens/ui-o-matic-3-1-for-umbraco-the-hacktoberfest-2020-edition-is-out-1i3d</guid>
      <description>&lt;p&gt;&lt;a href="https://hacktoberfest.digitalocean.com/"&gt;Hacktoberfest&lt;/a&gt; has come to an end... and it is super to see folks have contributed to  &lt;a href="https://github.com/TimGeyssens/UIOMatic"&gt;UI-O-Matic&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I merged all the pull requests in a 3.1 release!&lt;/p&gt;

&lt;h2&gt;
  
  
  What is new:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://timgeyssens.gitbook.io/ui-o-matic/02.defaulteditorviews#link"&gt;A Link Editor View&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://timgeyssens.gitbook.io/ui-o-matic/custom-sections"&gt;Custom Sections&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/TimGeyssens/UIOMatic/pull/168"&gt;Progress indicators on buttons&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://timgeyssens.gitbook.io/ui-o-matic/content-apps"&gt;Content Apps&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Again, thanks to the folks spending time on this and helping out in making this happen. &lt;/p&gt;

&lt;p&gt;Get it from nuget: &lt;a href="https://www.nuget.org/packages/Nibble.Umbraco.UIOMatic/"&gt;https://www.nuget.org/packages/Nibble.Umbraco.UIOMatic/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>umbraco</category>
      <category>hacktoberfest</category>
    </item>
    <item>
      <title>How to join the fun and get started with contributing to the umbraco community starter project/kit</title>
      <dc:creator>Tim Geyssens</dc:creator>
      <pubDate>Fri, 09 Oct 2020 08:19:35 +0000</pubDate>
      <link>https://dev.to/timgeyssens/how-to-join-the-fun-and-get-started-with-contributing-to-the-umbraco-community-starter-project-kit-36gn</link>
      <guid>https://dev.to/timgeyssens/how-to-join-the-fun-and-get-started-with-contributing-to-the-umbraco-community-starter-project-kit-36gn</guid>
      <description>&lt;p&gt;&lt;a href="https://hacktoberfest.digitalocean.com/"&gt;Hacktoberfest&lt;/a&gt; is in town! By submitting 4 prs to open source projects tagged with #hacktoberfest you can earn the necessary credits to complete the challenge! As a bonus Umbraco is also taking part ... read all about it on the blog &lt;a href="https://umbraco.com/blog/hacktoberfest-2020/"&gt;https://umbraco.com/blog/hacktoberfest-2020/&lt;/a&gt;, if you have 1 approved PR on any of the repo's that are marked by HQ you earn the credits for some swag (or plant a tree :) )&lt;/p&gt;

&lt;h1&gt;
  
  
  Umbraco Community starter project/kit
&lt;/h1&gt;

&lt;p&gt;Before october launched the idea of a community powered Umbraco v8 starter kit/project. We had/have discussions on the new &lt;a href="https://github.com/TimGeyssens/Umbraco-v8-Community-Website-StarterKit/discussions"&gt;Discussions feature&lt;/a&gt; of github and decided that we'll be building a site for a non profit org... So this project will be able to get used as inspiration or you can also deploy it for non profit orgs that are looking for an online presense...idea is that with a color palette picker we'll be able to change the look and feel to match the branding of the org.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;Sounds good right? So how can you get going?&lt;/p&gt;

&lt;p&gt;Fork, clone, and start contributing... you don't need VS if you are only interesed in design/frontend/docs&lt;/p&gt;

&lt;p&gt;Here is an intro to the PR process on Github&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/nT8KGYVurIU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Keeping your fork up to date
&lt;/h3&gt;

&lt;p&gt;Also make sure to keep your fork up to date (so if other peoples pr get's merged, you also need that in your fork)&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/deEYHVpE1c8"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Design
&lt;/h2&gt;

&lt;p&gt;An design start has been made: a mobile view of the homepage. &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%2Fi%2F7g2wmtxfbz63x6kzhuvy.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%2Fi%2F7g2wmtxfbz63x6kzhuvy.png" alt="Umbraco Community preview homepage mobile render" width="248" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can find preview render and the &lt;a href="https://www.figma.com/"&gt;figma&lt;/a&gt; source file here:&lt;br&gt;
&lt;a href=""&gt;https://github.com/TimGeyssens/Umbraco-v8-Community-Website-StarterKit/tree/main/design&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is a UI Kit with components :) &lt;br&gt;
&lt;a href="https://github.com/TimGeyssens/Umbraco-v8-Community-Website-StarterKit/tree/main/design/UI%20Kit"&gt;https://github.com/TimGeyssens/Umbraco-v8-Community-Website-StarterKit/tree/main/design/UI%20Kit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Design tasks are marked with "design" on the tracker: &lt;a href="https://github.com/TimGeyssens/Umbraco-v8-Community-Website-StarterKit/issues"&gt;https://github.com/TimGeyssens/Umbraco-v8-Community-Website-StarterKit/issues&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Frontend
&lt;/h2&gt;

&lt;p&gt;Frontend is currently setup with gulp and tailwind. But you should be able to contribute without visual studio. So also on Mac. &lt;/p&gt;

&lt;p&gt;You can find the markup in the &lt;a href="https://github.com/TimGeyssens/Umbraco-v8-Community-Website-StarterKit/tree/main/markup"&gt;markup&lt;/a&gt; folder.&lt;/p&gt;

&lt;p&gt;Tasks will also be added to the issue tracker and marked with "design".&lt;/p&gt;

&lt;h2&gt;
  
  
  Backend
&lt;/h2&gt;

&lt;p&gt;For this you'll need windows and visual studio. The solution can be found in the &lt;a href="https://github.com/TimGeyssens/Umbraco-v8-Community-Website-StarterKit/tree/main/source"&gt;source&lt;/a&gt; folder and contains 3 projets (core, web, tests)&lt;/p&gt;

&lt;p&gt;You just run the web project from vs and then from the backend (username/pw in readme) you first run a uSyn import to get everyting in sync! &lt;/p&gt;

&lt;p&gt;Tasks are marked with backend (and go from easy to more challenging). Key is here to keep to the best practises... &lt;/p&gt;

&lt;h2&gt;
  
  
  Docs
&lt;/h2&gt;

&lt;p&gt;And last but not least you can also write docs, those will be in the &lt;a href="https://github.com/TimGeyssens/Umbraco-v8-Community-Website-StarterKit/tree/main/source"&gt;docs&lt;/a&gt; folder.&lt;/p&gt;

&lt;p&gt;Now let's plant some trees!&lt;/p&gt;

</description>
      <category>umbraco</category>
      <category>hacktoberfest</category>
      <category>help</category>
    </item>
    <item>
      <title>The Last Crusade</title>
      <dc:creator>Tim Geyssens</dc:creator>
      <pubDate>Fri, 02 Oct 2020 15:28:46 +0000</pubDate>
      <link>https://dev.to/timgeyssens/the-last-crusade-3c92</link>
      <guid>https://dev.to/timgeyssens/the-last-crusade-3c92</guid>
      <description>&lt;p&gt;This is the final follow up to &lt;a&gt;Has Umbraco turned into the "tourist trap" of open source .net&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That caused some actions from HQ... Kim(the CEO) got in touch and I gave it 2 months where I didn't complain about anything... now at the &lt;a href="https://live.df20.nl/"&gt;DF20&lt;/a&gt; my session about this wasn't picked... but was invited for the roundtable that took place around umbraco community... getting the agenda 2 hours in advance, no questions that got collected from the community where asked.&lt;/p&gt;

&lt;p&gt;So I'll just stop trying to put energy in this but just wanted to share the thoughts I mailed Kim and Niels...&lt;/p&gt;

&lt;h2&gt;
  
  
  Mail1
&lt;/h2&gt;

&lt;p&gt;Hey Kim and Niels&lt;/p&gt;

&lt;p&gt;Since the kids are in bed now I have some time for a more detailed mail. As Niels might tell you I also reached out to him to make amends.&lt;/p&gt;

&lt;p&gt;From our phone call I can hear you are definitely full of ideas and have a vision for a bright Umbraco future. &lt;/p&gt;

&lt;p&gt;Since my "crusade" in March several people from the community reached out with several thoughts. And why not let that benefit you. &lt;/p&gt;

&lt;p&gt;I am totally not a business guy but I do have a good touch with the unique Umbraco community. I also think I can give you some special insights... (since I know you aren't a twitter/slack guy). I've been around for a loooong time and also know of several HQ aspects.&lt;/p&gt;

&lt;p&gt;But my true passion is for the community&lt;/p&gt;

&lt;p&gt;Like look at this one &lt;a href="https://twitter.com/timgeyssens/status/1296782437603848195/photo/1"&gt;https://twitter.com/timgeyssens/status/1296782437603848195/photo/1&lt;/a&gt;1 , it is a picture from CG 2009 where the MVP's are announced.&lt;/p&gt;

&lt;p&gt;What I find cool about this is that 4 of those folks are stil active with Umbraco. Got me thinking why is that? Since that is a long time to be using a piece of software. &lt;/p&gt;

&lt;p&gt;For me personally Umbraco (and Niels of course) showed me that you can be a developer and also have fun (not just be the dull guy behind the computer). So even though there might be mistakes along the road what get's people to stay for the long term and not look back is that unique fun community feeling. &lt;/p&gt;

&lt;p&gt;I'm sure you have seen/felt it at cg, but cg is a small subset of the community. And especially with this years COVID situation even that subset missed out...&lt;/p&gt;

&lt;p&gt;But a digital business that can have these long standing relationships... that is golden I think. &lt;/p&gt;

&lt;p&gt;Those cheesy quirky things are what put a smile on peoples faces... and that is a powerful to do. &lt;/p&gt;

&lt;p&gt;Another thing from this week is this: &lt;a href="https://twitter.com/KieronBos/status/1296379233351143426"&gt;https://twitter.com/KieronBos/status/1296379233351143426&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kieron contacted me on slack about a simple question, will a package I created (configtree) work on umbraco v... (conversation as attachment , diffent screenshots from slack, sorry don't know how to get 1 big one)&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%2Fi%2Fdabmgk8notv7c7jhcvof.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%2Fi%2Fdabmgk8notv7c7jhcvof.png" alt="Alt Text" width="800" height="541"&gt;&lt;/a&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%2Fi%2Fy1t9k7yylj8m6zhtnkp6.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%2Fi%2Fy1t9k7yylj8m6zhtnkp6.png" alt="Alt Text" width="800" height="544"&gt;&lt;/a&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%2Fi%2F5gdu06t4m2xhlqu7tf3d.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%2Fi%2F5gdu06t4m2xhlqu7tf3d.png" alt="Alt Text" width="800" height="582"&gt;&lt;/a&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%2Fi%2Fj8pf06tohz9es7kbt4mn.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%2Fi%2Fj8pf06tohz9es7kbt4mn.png" alt="Alt Text" width="800" height="590"&gt;&lt;/a&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%2Fi%2F0rgkxi2l7yw54ouojk4p.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%2Fi%2F0rgkxi2l7yw54ouojk4p.png" alt="Alt Text" width="800" height="583"&gt;&lt;/a&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%2Fi%2Fp7y7mzqajdpup57b31jv.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%2Fi%2Fp7y7mzqajdpup57b31jv.png" alt="Alt Text" width="800" height="592"&gt;&lt;/a&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%2Fi%2Fii4xr4vpd8sy57dxsf0e.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%2Fi%2Fii4xr4vpd8sy57dxsf0e.png" alt="Alt Text" width="800" height="588"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So as you can see I went the extra mile and also had some fun while helping him. I think Kieron will definitely remember this now and maybe even cut me some slack when something goes wrong, or turn his positive energy towards another community member.&lt;/p&gt;

&lt;p&gt;Niels even had a Umbraco is % amount crazy on the site once (dunno if that is still on there), but that is what makes umbraco standout.&lt;/p&gt;

&lt;p&gt;Will there be tech options in the future that are cooler, probably... &lt;br&gt;
Options that are more feature rich, probably...&lt;br&gt;
Options that are better priced... of course &lt;/p&gt;

&lt;p&gt;That is a hard thing to predict and keep ahead of competition... &lt;/p&gt;

&lt;p&gt;So I truly believe that you need to focus on having people committed and not even think about looking at other options. If you keep the Umbraco spirit and go the extra mile... people will stay on board for the long run. And they will also evangelize the platform with other folks...&lt;/p&gt;

&lt;p&gt;If you know where to look, the community is full of great ideas &lt;a href="https://twitter.com/Steve_Gibe/status/1296816981493141504"&gt;https://twitter.com/Steve_Gibe/status/1296816981493141504&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Surprise them, act on an impulse... and I'm betting most will forget the frustrations and cut you some slack.&lt;/p&gt;

&lt;p&gt;So make the Umglourious Basterds happen, it might be hard to measure the ROI but I do think it is worth a try. And I think the community would be surprised if you announced that you turned the annoying guy into a valuable asset. &lt;/p&gt;

&lt;p&gt;Again give it some thought, I don't expect you to get back to me immediately  ... a community that would go through fire is priceless.&lt;/p&gt;

&lt;p&gt;Have a good weekend!&lt;br&gt;
Cheers,&lt;br&gt;
Tim&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow up mail:
&lt;/h2&gt;

&lt;p&gt;Final idea on the Umglourious Basterds&lt;/p&gt;

&lt;p&gt;Nothing set in stone of course but imagine having the team like this:&lt;/p&gt;

&lt;p&gt;Chief Unicorn (Niels): making sure the community is up to date with the folk at HQ, like videos with HQ members showing what the job they do consists of...&lt;br&gt;
Hacker (Warren): having Warren spend a day a week exploring the new features, ecxiting hack options with new tech...blogging about that on .com (so it has more reach then twitter)&lt;br&gt;
Instructor (Paul Seal , from codeshare.co.uk): sponsor him to keep his youtube channel populated with new umbraco focused tutorials, how much he needs to get some vids a week, dunno...maybe 1 day of work in a week...&lt;br&gt;
1st Lieutenant (Me): taking care of the frustration "killing" and overal keep the good vibe and crazyness in the Umbraco community.&lt;/p&gt;

&lt;p&gt;Make a fun photoshop collage of them and ...&lt;/p&gt;

&lt;h2&gt;
  
  
  Response
&lt;/h2&gt;

&lt;p&gt;Kim is a nice guy no doubt about it... and we want the same end result, a great Umbraco community... with a super succesfull HQ backing it...&lt;/p&gt;

&lt;p&gt;But the response was: great ideas but no budget... &lt;/p&gt;

&lt;p&gt;Guess &lt;a href="https://umbraco.com/blog/new-hire-at-umbraco-hq-pernille-stausboell/"&gt;Marketing&lt;/a&gt; comes at the first place... &lt;/p&gt;

&lt;p&gt;Will I still using Umbraco, yes... still passionate about the project? Not that much, some side stuff like &lt;a href="https://umbraco.com/blog/hacktoberfest-2020/"&gt;hacktober fest&lt;/a&gt; and the &lt;a href="https://umbraco.com/blog/the-umbraco-package-team/"&gt;package team&lt;/a&gt; + &lt;a href="https://umbraco.com/blog/?tag=.net%20core"&gt;the .net core version&lt;/a&gt; spark my interest but for the rest it's just a plain software comp now... &lt;/p&gt;

</description>
      <category>umbraco</category>
    </item>
  </channel>
</rss>
