<?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: Andreas</title>
    <description>The latest articles on DEV Community by Andreas (@azettl).</description>
    <link>https://dev.to/azettl</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%2F174695%2F73530a98-5d01-4f2e-afab-34b02eca58ad.jpg</url>
      <title>DEV Community: Andreas</title>
      <link>https://dev.to/azettl</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/azettl"/>
    <language>en</language>
    <item>
      <title>Building an Open Floor Parrot Agent in Python</title>
      <dc:creator>Andreas</dc:creator>
      <pubDate>Sat, 19 Jul 2025 12:29:03 +0000</pubDate>
      <link>https://dev.to/azettl/building-an-open-floor-parrot-agent-in-python-4383</link>
      <guid>https://dev.to/azettl/building-an-open-floor-parrot-agent-in-python-4383</guid>
      <description>&lt;p&gt;In this short guide, we will build a simple parrot agent together using Python. The parrot agent will simply repeat everything you send him and a small 🦜 emoji in front of the return. We will create the &lt;a href="https://github.com/open-voice-interoperability/openfloor-docs" rel="noopener noreferrer"&gt;Open Floor Protocol&lt;/a&gt;-compliant agent with the help of the &lt;a href="https://test.pypi.org/project/openfloor/" rel="noopener noreferrer"&gt;openfloor&lt;/a&gt; Python package.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial Setup
&lt;/h2&gt;

&lt;p&gt;First, let's set up our project by creating the project folder and installing the required packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;parrot-agent
&lt;span class="nb"&gt;cd &lt;/span&gt;parrot-agent
python &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate  &lt;span class="c"&gt;# On Windows: venv\Scripts\activate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;requirements.txt&lt;/code&gt; file with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--extra-index-url https://test.pypi.org/simple/
events==0.5
jsonpath-ng&amp;gt;=1.5.0
openfloor
flask
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then install the dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that the basic setup is done, let us start coding together!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Building the Parrot Agent Class
&lt;/h2&gt;

&lt;p&gt;Let's create our main agent file. Create a new file &lt;code&gt;parrot_agent.py&lt;/code&gt;, this will contain the main logic of our agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1.1: Add the imports
&lt;/h3&gt;

&lt;p&gt;Let's start with importing everything we need from the &lt;code&gt;openfloor&lt;/code&gt; package, add them at the top of your &lt;code&gt;parrot_agent.py&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openfloor&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;BotAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Manifest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Identification&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Capability&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;SupportedLayers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;UtteranceEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Envelope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DialogEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TextFeature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;To&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;PublishManifestsEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Parameters&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why these imports?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;BotAgent&lt;/code&gt; - The base class we'll extend&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;Manifest&lt;/code&gt; - To define our agent's capabilities and identification&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;UtteranceEvent&lt;/code&gt; - The type of event we'll handle for text messages&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;Envelope&lt;/code&gt; - Container for Open Floor messages&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;DialogEvent&lt;/code&gt; and &lt;code&gt;TextFeature&lt;/code&gt; - To create text responses&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;To&lt;/code&gt; and &lt;code&gt;Sender&lt;/code&gt; - For message addressing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1.2: Start the ParrotAgent class
&lt;/h3&gt;

&lt;p&gt;Now let's start creating our &lt;code&gt;ParrotAgent&lt;/code&gt; class by extending the &lt;code&gt;BotAgent&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParrotAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BotAgent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    ParrotAgent - A simple agent that echoes back whatever it receives
    Extends BotAgent to provide parrot functionality
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Manifest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🦜 Parrot Agent initialized with speaker URI: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speakerUri&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What we just did:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Created a class that extends &lt;code&gt;BotAgent&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  Added a constructor that takes a manifest and passes it to the parent class&lt;/li&gt;
&lt;li&gt;  The manifest will define what our agent can do&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1.3: Override the utterance handler
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;BotAgent&lt;/code&gt; class provides a default &lt;code&gt;bot_on_utterance&lt;/code&gt; method that we need to override. This is where the magic happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bot_on_utterance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UtteranceEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in_envelope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Envelope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out_envelope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Envelope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        Override the utterance handler to provide parrot functionality
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🦜 Processing utterance event&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Extract the dialog event from the utterance parameters
&lt;/span&gt;            &lt;span class="n"&gt;dialog_event_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dialogEvent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;dialog_event_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_send_error_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*chirp* I didn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t receive a valid dialog event!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out_envelope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;

            &lt;span class="c1"&gt;# Convert to DialogEvent object if it's a dictionary
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dialog_event_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;dialog_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DialogEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dialog_event_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dialog_event_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DialogEvent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;dialog_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dialog_event_data&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_send_error_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*chirp* I didn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t receive a valid dialog event!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out_envelope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;

            &lt;span class="c1"&gt;# Check if there's a text feature
&lt;/span&gt;            &lt;span class="n"&gt;text_feature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dialog_event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;text_feature&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text_feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tokens&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;text_feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_send_error_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*chirp* I can only repeat text messages!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out_envelope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;

            &lt;span class="c1"&gt;# Extract the original text from tokens
&lt;/span&gt;            &lt;span class="n"&gt;original_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;text_feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Create parrot response with emoji prefix
&lt;/span&gt;            &lt;span class="n"&gt;parrot_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🦜 &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;original_text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

            &lt;span class="c1"&gt;# Create the response dialog event
&lt;/span&gt;            &lt;span class="n"&gt;response_dialog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DialogEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;speakerUri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TextFeature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;parrot_text&lt;/span&gt;&lt;span class="p"&gt;])}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Create and add the utterance event to the response
&lt;/span&gt;            &lt;span class="n"&gt;response_utterance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UtteranceEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;dialogEvent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;response_dialog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;speakerUri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;in_envelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;out_envelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response_utterance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🦜 Echoing back: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;parrot_text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🦜 Error in parrot utterance handling: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_send_error_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*confused chirp* Something went wrong while trying to repeat that!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out_envelope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The parroting logic:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Extract the dialog event from the utterance parameters&lt;/li&gt;
&lt;li&gt;  Get the text feature and extract text from tokens&lt;/li&gt;
&lt;li&gt;  Add the 🦜 emoji prefix&lt;/li&gt;
&lt;li&gt;  Create a dialog event response&lt;/li&gt;
&lt;li&gt;  Send it back to the original sender&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1.4: Add helper methods
&lt;/h3&gt;

&lt;p&gt;Let's add the helper methods we called in &lt;code&gt;bot_on_utterance&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_send_error_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out_envelope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Envelope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Helper method to send error responses&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;error_dialog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DialogEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;speakerUri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TextFeature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;])}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;error_utterance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UtteranceEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;dialogEvent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;error_dialog&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;out_envelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error_utterance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1.5: Override the manifest handler
&lt;/h3&gt;

&lt;p&gt;We also need to handle manifest requests properly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bot_on_get_manifests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in_envelope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Envelope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out_envelope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Envelope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        Handle manifest requests by sending our capabilities
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🦜 Sending manifest information&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Create the publish manifests response
&lt;/span&gt;        &lt;span class="n"&gt;publish_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PublishManifestsEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;servicingManifests&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_manifest&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;discoveryManifests&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
            &lt;span class="p"&gt;}),&lt;/span&gt;
            &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;speakerUri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;in_envelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;out_envelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;publish_event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1.6: Add the factory function
&lt;/h3&gt;

&lt;p&gt;After the class, add this factory function with the default configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_parrot_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;speaker_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;service_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Parrot Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;organization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OpenFloor Demo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A simple parrot agent that echoes back messages with a 🦜 emoji&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ParrotAgent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Factory function to create a ParrotAgent with default configuration
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# Create the identification
&lt;/span&gt;    &lt;span class="n"&gt;identification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Identification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;speakerUri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;speaker_uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;serviceUrl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;service_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;organization&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;organization&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;conversationalName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;synopsis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Create the capabilities
&lt;/span&gt;    &lt;span class="n"&gt;capability&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Capability&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;keyphrases&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;echo&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;repeat&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;parrot&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;say&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;descriptions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Echoes back any text message with a 🦜 emoji&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Repeats user input verbatim&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Simple text mirroring functionality&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;supportedLayers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;SupportedLayers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Create the manifest
&lt;/span&gt;    &lt;span class="n"&gt;manifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Manifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;identification&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;identification&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;capability&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ParrotAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this factory does:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Takes configuration options with some defaults&lt;/li&gt;
&lt;li&gt;  Creates an identification object that describes our agent&lt;/li&gt;
&lt;li&gt;  Defines capabilities that tell others what our agent can do&lt;/li&gt;
&lt;li&gt;  Creates a manifest that combines identification and capabilities&lt;/li&gt;
&lt;li&gt;  Returns a new ParrotAgent instance&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 2: Building the Flask Server
&lt;/h2&gt;

&lt;p&gt;The agent itself is done, but how to talk to it? We need to build our Flask server for this, so start with creating a &lt;code&gt;server.py&lt;/code&gt; file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2.1: Add imports
&lt;/h3&gt;

&lt;p&gt;Add these imports at the top:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jsonify&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;parrot_agent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_parrot_agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openfloor&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Envelope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Payload&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2.2: Create the Flask app
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Configure CORS for specific origin
&lt;/span&gt;&lt;span class="nd"&gt;@app.after_request&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;after_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;allowed_origin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http://127.0.0.1:4000&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;origin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Origin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;origin&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;allowed_origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allowed_origin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Access-Control-Allow-Methods&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST, OPTIONS&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Access-Control-Allow-Headers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OPTIONS&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_options&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Handle preflight OPTIONS requests&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this CORS setup?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Only allows requests from the specific domain&lt;/li&gt;
&lt;li&gt;  Handles preflight &lt;code&gt;OPTIONS&lt;/code&gt; requests&lt;/li&gt;
&lt;li&gt;  Restricts to &lt;code&gt;POST&lt;/code&gt; methods and the &lt;code&gt;Content-Type&lt;/code&gt; header&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2.3: Create the agent instance
&lt;/h3&gt;

&lt;p&gt;Now we need to create our parrot by using the factory function &lt;code&gt;create_parrot_agent&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create the parrot agent instance
&lt;/span&gt;&lt;span class="n"&gt;parrot_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_parrot_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;speaker_uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tag:openfloor-demo.com,2025:parrot-agent&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;service_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SERVICE_URL&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8080/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Polly the Parrot&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;organization&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OpenFloor Demo Corp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;A friendly parrot that repeats everything you say!&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🦜 Parrot agent created: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;parrot_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speakerUri&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2.4: Build the main endpoint step by step
&lt;/h3&gt;

&lt;p&gt;Now we have the agent and the Flask app, but the most important part is still missing, and that's our endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_openfloor_message&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Main Open Floor Protocol endpoint&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🦜 Received request: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Validate the incoming payload
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Invalid JSON payload&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;

        &lt;span class="c1"&gt;# Parse the payload
&lt;/span&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;openFloor&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Direct payload format
&lt;/span&gt;                &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;incoming_envelope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;openFloor&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Direct envelope format
&lt;/span&gt;                &lt;span class="n"&gt;incoming_envelope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Envelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;parse_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🦜 Parsing error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;parse_error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Invalid OpenFloor payload format&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;details&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parse_error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🦜 Processing envelope from: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;incoming_envelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speakerUri&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Process the envelope through the parrot agent
&lt;/span&gt;        &lt;span class="n"&gt;outgoing_envelope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parrot_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_envelope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;incoming_envelope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Create response payload
&lt;/span&gt;        &lt;span class="n"&gt;response_payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Payload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;openFloor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;outgoing_envelope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response_payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🦜 Sending response: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🦜 Error processing request: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Internal server error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What's happening here:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Validate that we received valid JSON&lt;/li&gt;
&lt;li&gt;  Parse the payload into an Open Floor envelope&lt;/li&gt;
&lt;li&gt;  Process it through our parrot agent&lt;/li&gt;
&lt;li&gt;  Create and send the response as a properly formatted payload&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3: Creating the Entry Point
&lt;/h2&gt;

&lt;p&gt;We end by creating a simple &lt;code&gt;main.py&lt;/code&gt; as our entry point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PORT&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🦜 Parrot Agent server starting on port &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Final Setup
&lt;/h2&gt;

&lt;p&gt;Your project structure should now look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;parrot-agent/
├── requirements.txt
├── parrot_agent.py
├── server.py
└── main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Test Your Implementation
&lt;/h2&gt;

&lt;p&gt;Run this to test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send your manifest or utterance requests to &lt;code&gt;http://localhost:8080/&lt;/code&gt; to see if it's working! You can also download the simple single HTML file manifest and utterance chat &lt;a href="https://github.com/azettl/openfloor-js-chat" rel="noopener noreferrer"&gt;azettl/openfloor-js-chat&lt;/a&gt; to test your agent locally.&lt;/p&gt;

&lt;p&gt;If you found this guide useful, follow me for more and let me know what you build with it in the comments!&lt;/p&gt;

</description>
      <category>openfloor</category>
      <category>ai</category>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>Building an Open Floor Parrot Agent</title>
      <dc:creator>Andreas</dc:creator>
      <pubDate>Sat, 19 Jul 2025 00:44:54 +0000</pubDate>
      <link>https://dev.to/azettl/building-an-open-floor-parrot-agent-3e79</link>
      <guid>https://dev.to/azettl/building-an-open-floor-parrot-agent-3e79</guid>
      <description>&lt;p&gt;In this short guide, we will build a simple parrot agent together. The parrot agent will simply repeat everything you send him and a small 🦜 emoji in front of the return. We will create the &lt;a href="https://github.com/open-voice-interoperability/openfloor-docs" rel="noopener noreferrer"&gt;Open Floor Protocol&lt;/a&gt;-compliant agent with the help of the &lt;a href="https://www.npmjs.com/package/@openfloor/protocol" rel="noopener noreferrer"&gt;@openfloor/protocol&lt;/a&gt; package.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial Setup
&lt;/h2&gt;

&lt;p&gt;First, let's set up our project by creating the project folder and installing the required packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;parrot-agent
&lt;span class="nb"&gt;cd &lt;/span&gt;parrot-agent
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;express @openfloor/protocol
npm  &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; typescript @types/node @types/express ts-node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will also need a TypeScript configuration file, so create &lt;code&gt;tsconfig.json&lt;/code&gt; and add the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ES2020"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"commonjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lib"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ES2020"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"outDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rootDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"esModuleInterop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skipLibCheck"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"forceConsistentCasingInFileNames"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"resolveJsonModule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"declaration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"declarationMap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sourceMap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/**/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exclude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"node_modules"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dist"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that the basic set up is done, let us start coding together!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Building the Parrot Agent Class
&lt;/h2&gt;

&lt;p&gt;Before we create our parrot agent class, let's create a new folder &lt;code&gt;src&lt;/code&gt; where we will store all of our files.&lt;/p&gt;

&lt;p&gt;Create a new file &lt;code&gt;src/parrot-agent.ts&lt;/code&gt;, this will contain the main logic of our agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1.1: Add the imports
&lt;/h3&gt;

&lt;p&gt;Lets start with the import of everything we need from the &lt;code&gt;@openfloor/protocol&lt;/code&gt; package, add them at the top of your &lt;code&gt;parrot-agent.ts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nx"&gt;BotAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;ManifestOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;UtteranceEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;Envelope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createTextUtterance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isUtteranceEvent&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@openfloor/protocol&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why these imports?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;BotAgent&lt;/code&gt; - The base class we'll extend&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;ManifestOptions&lt;/code&gt; - To define our agent's capabilities&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;UtteranceEvent&lt;/code&gt; - The type of event we'll handle&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;Envelope&lt;/code&gt; - Container for Open Floor messages&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;createTextUtterance&lt;/code&gt; - Helper to create text responses&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;isUtteranceEvent&lt;/code&gt; - To check if an event is an utterance&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1.2: Start the ParrotAgent class
&lt;/h3&gt;

&lt;p&gt;Now let's start creating our &lt;code&gt;ParrotAgent&lt;/code&gt; class by extending the &lt;code&gt;BotAgent&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * ParrotAgent - A simple agent that echoes back whatever it receives
 * Extends BotAgent to provide parrot functionality
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParrotAgent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BotAgent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ManifestOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What we just did:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Created a class that extends &lt;code&gt;BotAgent&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  Added a constructor that takes a manifest and passes it to the parent class&lt;/li&gt;
&lt;li&gt;  The manifest will define what our agent can do&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1.3: Override the processEnvelope method
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;processEnvelope&lt;/code&gt; method of the &lt;code&gt;BotAgent&lt;/code&gt; class is the main entry point for agent message processing. So, this is where the magic happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="cm"&gt;/**
   * Override the processEnvelope method to handle parrot functionality
   */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;processEnvelope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;incomingEnvelope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Envelope&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Envelope&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's build the method body step by step. First, create an array to store our responses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseEvents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we loop through each event in the incoming envelope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;incomingEnvelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also should check if this event is meant for us. So, add this inside the loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;      &lt;span class="c1"&gt;// Check if this event is addressed to us&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addressedToMe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; 
        &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speakerUri&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speakerUri&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; 
        &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;serviceUrl&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;serviceUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this check?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;!event.to&lt;/code&gt; - If no recipient is specified, it's for everyone.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;event.to.speakerUri === this.speakerUri&lt;/code&gt; - Direct message to us&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;event.to.serviceUrl === this.serviceUrl&lt;/code&gt; - Message to our service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this check, we know the event is really meant for us, and we can now handle the two types of events we care about:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;addressedToMe&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;isUtteranceEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_handleParrotUtterance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;incomingEnvelope&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;responseEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseEvent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;addressedToMe&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;getManifests&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// We respond to the getManifests event with the publishManifest event&lt;/span&gt;
        &lt;span class="nx"&gt;responseEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;eventType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;publishManifest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="c1"&gt;// We use the senders speakerUri as the recipient&lt;/span&gt;
          &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;incomingEnvelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speakerUri&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;servicingManifests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toObject&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What's happening here:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  If it's a text message (utterance), we'll handle it with our parrot logic&lt;/li&gt;
&lt;li&gt;  If someone asks for our capabilities via the &lt;code&gt;getManifests&lt;/code&gt; event, we send back our manifest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To finish the method, we can now close the loop and return an envelope as a response with all the required response events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Create response envelope with all response events&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Envelope&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;incomingEnvelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;incomingEnvelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;serviceUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;serviceUrl&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;responseEvents&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1.4: Implement the parrot logic
&lt;/h3&gt;

&lt;p&gt;You saw in the &lt;code&gt;processEnvelope&lt;/code&gt; method that we call a yet undefined &lt;code&gt;_handleParrotUtterance&lt;/code&gt;, this is the private method we will now implement to echo back what we got sent via the &lt;code&gt;utterance&lt;/code&gt; event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="cm"&gt;/**
   * Handle utterance events by echoing them back
   */&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;_handleParrotUtterance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UtteranceEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nx"&gt;incomingEnvelope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Envelope&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, let's try to extract the dialog event from the utterance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dialogEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;dialogEvent&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;dialogEvent&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;dialogEvent&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;dialogEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;dialogEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createTextUtterance&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🦜 *chirp* I didn't receive a valid dialog event!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;incomingEnvelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speakerUri&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What we're doing:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Extracting the dialog event from the utterance parameters&lt;/li&gt;
&lt;li&gt;  Checking if it has the structure we expect&lt;/li&gt;
&lt;li&gt;  If not, sending a friendly error message&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, as we know we are dealing with a valid dialog event, we can try to get the text from it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textFeature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dialogEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;textFeature&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;textFeature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;textFeature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// No text to parrot, send a default response&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createTextUtterance&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🦜 *chirp* I can only repeat text messages!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;incomingEnvelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speakerUri&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We only handle text, so as you see also here, we would return early with an &lt;code&gt;createTextUtterance&lt;/code&gt; and a generic message if the &lt;code&gt;textFeature&lt;/code&gt; is not how we expect it.&lt;/p&gt;

&lt;p&gt;But now everything should be valid, and we can go for the actual parroting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;      &lt;span class="c1"&gt;// Combine all token values to get the full text&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;textFeature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokens&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Create parrot response with emoji prefix&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parrotText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`🦜 &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;originalText&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createTextUtterance&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;parrotText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;incomingEnvelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speakerUri&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="c1"&gt;// Parrot is very confident in repeating!&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The parroting logic:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Extract text from tokens by mapping over them and joining&lt;/li&gt;
&lt;li&gt;  Add the 🦜 emoji prefix&lt;/li&gt;
&lt;li&gt;  Create a text utterance response&lt;/li&gt;
&lt;li&gt;  Set confidence to 1.0 because 🦜 are confident!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, we can add some error handling and close the method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error in parrot utterance handling:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// Send error response&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createTextUtterance&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🦜 *confused chirp* Something went wrong while trying to repeat that!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;incomingEnvelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speakerUri&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only thing that is left to do is close the class with a closing brace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1.5: Add the factory function
&lt;/h3&gt;

&lt;p&gt;After the class, add this factory function with the default configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Factory function to create a ParrotAgent with default configuration
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createParrotAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;serviceUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;organization&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;description&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nx"&gt;ParrotAgent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;serviceUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Parrot Agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;organization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OpenFloor Demo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A simple parrot agent that echoes back messages with a 🦜 emoji&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ManifestOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;identification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;serviceUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;organization&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;conversationalName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;synopsis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;keyphrases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;echo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;repeat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;parrot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;say&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;descriptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Echoes back any text message with a 🦜 emoji&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Repeats user input verbatim&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Simple text mirroring functionality&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ParrotAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this factory does:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Takes configuration options&lt;/li&gt;
&lt;li&gt;  Provides some defaults&lt;/li&gt;
&lt;li&gt;  Creates a manifest that describes our agent's capabilities&lt;/li&gt;
&lt;li&gt;  Returns a new ParrotAgent instance&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 2: Building the Express Server
&lt;/h2&gt;

&lt;p&gt;The agent itself is done, but how to talk to it? We need to build our express server for this, so start with creating a &lt;code&gt;src/server.ts&lt;/code&gt; file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2.1: Add imports
&lt;/h3&gt;

&lt;p&gt;Add these imports at the top:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createParrotAgent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./parrot-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nx"&gt;validateAndParsePayload&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@openfloor/protocol&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2.2: Create the Express app
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2.3: Add CORS middleware
&lt;/h3&gt;

&lt;p&gt;You might want to add a CORS configuration to allow access to your agent from different origins:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// CORS middleware for http://127.0.0.1:4000&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allowedOrigin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://127.0.0.1:4000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;allowedOrigin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;allowedOrigin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Access-Control-Allow-Methods&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST, OPTIONS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Access-Control-Allow-Headers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OPTIONS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this CORS setup?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Only allows requests from the specific domain&lt;/li&gt;
&lt;li&gt;  Handles preflight &lt;code&gt;OPTIONS&lt;/code&gt; requests&lt;/li&gt;
&lt;li&gt;  Restricts to &lt;code&gt;POST&lt;/code&gt; methods and the &lt;code&gt;Content-Type&lt;/code&gt; header&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2.4: Create the agent instance
&lt;/h3&gt;

&lt;p&gt;Now we need to create our parrot by using the factory function &lt;code&gt;createParrotAgent&lt;/code&gt;. Important is that the &lt;code&gt;serviceUrl&lt;/code&gt; matches your server endpoint; otherwise our agent will deny the request (remember the check we added in section 1.3).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create the parrot agent instance&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parrotAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createParrotAgent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag:openfloor-demo.com,2025:parrot-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;serviceUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SERVICE_URL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:8080/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Polly the Parrot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;organization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OpenFloor Demo Corp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A friendly parrot that repeats everything you say!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2.5: Build the main endpoint step by step
&lt;/h3&gt;

&lt;p&gt;Now we have the agent and the Express app, but the most important part is still missing, and that's our endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Main Open Floor Protocol endpoint&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Received request:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, let's validate the incoming payload with the &lt;code&gt;validateAndParsePayload&lt;/code&gt; function from the &lt;code&gt;@openfloor/protocol&lt;/code&gt; package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="c1"&gt;// Validate and parse the incoming payload&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validationResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validateAndParsePayload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;validationResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Validation errors:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validationResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid OpenFloor payload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;validationResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we know the payload is valid, and we can extract the envelope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;validationResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;incomingEnvelope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;openFloor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Processing envelope from:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;incomingEnvelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speakerUri&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then let's process the envelope through our parrot agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="c1"&gt;// Process the envelope through the parrot agent&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outgoingEnvelope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;parrotAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;processEnvelope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;incomingEnvelope&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the processing, we can create and send the response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="c1"&gt;// Create response payload&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responsePayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;outgoingEnvelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toPayload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;responsePayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toObject&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sending response:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we add the catch block and close the endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error processing request:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Internal server error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unknown error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2.6: Export the app
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Creating the Entry Point
&lt;/h2&gt;

&lt;p&gt;We end by creating a simple &lt;code&gt;src/index.ts&lt;/code&gt; as our entry point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Parrot Agent server running on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Final Setup
&lt;/h2&gt;

&lt;p&gt;Add or overwrite these scripts in the existing &lt;code&gt;scripts&lt;/code&gt; object in your &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node dist/index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ts-node src/index.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsc"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Test Your Implementation
&lt;/h2&gt;

&lt;p&gt;Run this to test:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Send your manifest or utterance requests to &lt;code&gt;http://localhost:8080/&lt;/code&gt; to see if it's working! You can also download the simple single HTML file manifest and utterance chat &lt;a href="https://github.com/azettl/openfloor-js-chat" rel="noopener noreferrer"&gt;azettl/openfloor-js-chat&lt;/a&gt; to test your agent locally.&lt;/p&gt;

&lt;p&gt;If you found this guide useful follow me for more and let me know what you build with it in the comments!&lt;/p&gt;

</description>
      <category>openfloor</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>📱 Introducing my latest sideproject: Fridge Leftovers AI 🍲</title>
      <dc:creator>Andreas</dc:creator>
      <pubDate>Thu, 14 Mar 2024 17:14:18 +0000</pubDate>
      <link>https://dev.to/azettl/introducing-my-latest-sideproject-fridge-leftovers-ai-2h7n</link>
      <guid>https://dev.to/azettl/introducing-my-latest-sideproject-fridge-leftovers-ai-2h7n</guid>
      <description>&lt;p&gt;Ever found yourself staring at the contents of your fridge, wondering what to cook with the leftovers? Say hello to my new app Fridge Leftovers AI, it simplifies meal planning by analyzing photos of your fridge contents and suggests delicious recipes! 📸🥘&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fhirmdf9y3hduawe5mm4t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fhirmdf9y3hduawe5mm4t.png" alt="Screenshot of the Ingredients Screen of the APP" width="405" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🎓 What I Learned in this sideproject until now:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React Native for cross-platform development.&lt;/li&gt;
&lt;li&gt;The process of developing, building, and submitting an app using Expo.&lt;/li&gt;
&lt;li&gt;App Store Connect Backend for app management.&lt;/li&gt;
&lt;li&gt;Sharpened my AI prompting skills.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Curious to see what culinary wonders await in your fridge? Try out Fridge Leftovers AI today! 🌟 The first 15 pictures are on the house! 🆓 &lt;a href="https://fridgeleftoversai.com/" rel="noopener noreferrer"&gt;https://fridgeleftoversai.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And hey, I'd love to hear your thoughts and feedback on the app! Your input helps me make Fridge Leftovers AI even better. Feel free to drop your comments below or shoot me a message. &lt;/p&gt;

&lt;p&gt;And if you don't have iOS but still want to help, I search for realistic images of open fridges to further test my app. If you want to know what you can cook with the ingredients left in your fridge or if the AI detects anything at all, please comment with a picture, and I will tell you what you can cook with it.&lt;/p&gt;

&lt;p&gt;Cheers! 📝🤗&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>openai</category>
      <category>ios</category>
    </item>
    <item>
      <title>Magazine the WordPress Plugin for PrintCSS</title>
      <dc:creator>Andreas</dc:creator>
      <pubDate>Fri, 26 Mar 2021 01:28:06 +0000</pubDate>
      <link>https://dev.to/azettl/magazine-the-wordpress-plugin-for-printcss-46in</link>
      <guid>https://dev.to/azettl/magazine-the-wordpress-plugin-for-printcss-46in</guid>
      <description>&lt;p&gt;In the last few weeks, I am working on a &lt;a href="https://gumroad.com/l/wp-magazine-printcss-cloud" rel="noopener noreferrer"&gt;WordPress Plugin&lt;/a&gt;, which allows you to generate PDFs from one or multiple posts/pages. Initially, it was designed to work only with my &lt;a href="https://printcss.cloud/" rel="noopener noreferrer"&gt;PrintCSS Cloud&lt;/a&gt;, which I introduced in the &lt;a href="https://printcss.blog/introducing-printcss-cloud-686622f4042a" rel="noopener noreferrer"&gt;previous article&lt;/a&gt;, but now the plugin is opened to any rendering tool you want to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation and Setup
&lt;/h2&gt;

&lt;p&gt;All you need to do to install the plugin is download it from &lt;a href="https://gumroad.com/l/wp-magazine-printcss-cloud" rel="noopener noreferrer"&gt;Gumroad&lt;/a&gt;. You can pay what you want starting from &lt;strong&gt;$0&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;Once you installed the ZIP file via the WordPress ‘Upload Plugins’ function or placed the extracted folder in your ‘wp-content/plugins’ directory, you need to activate the plugin.&lt;/p&gt;

&lt;p&gt;After the installation, you will need to provide an API key or locally installed rendering tool command on the settings page under ‘Settings’ &amp;gt; ‘Magazine’.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fkc33f3wcmbdbh65pgz47.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fkc33f3wcmbdbh65pgz47.png" alt="The Settings of the Magazine Plugin" width="800" height="426"&gt;&lt;/a&gt;&lt;em&gt;The Settings of the Magazine Plugin&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On the settings screen, you find the following options.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rendering Tool:&lt;/strong&gt; Depending on the filled API Key and Local Command Options, you see the supported rendering tools here. You need to use at least one of the API or the local command options for a rendering tool to appear in this selection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;RapidAPI Key:&lt;/strong&gt; To send the PrintCSS Cloud API request, you &lt;a href="https://rapidapi.com/azettl/api/printcss-cloud/pricing" rel="noopener noreferrer"&gt;need to subscribe to a plan on RapidAPI&lt;/a&gt;. With this, you get the API key required to authenticate with the PrintCSS Cloud REST service. The PrintCSS Cloud supports the rendering tools &lt;a href="https://weasyprint.org/" rel="noopener noreferrer"&gt;WeasyPrint&lt;/a&gt;, &lt;a href="https://www.pagedjs.org/" rel="noopener noreferrer"&gt;PagedJS&lt;/a&gt;, and &lt;a href="https://vivliostyle.org/" rel="noopener noreferrer"&gt;Vivliostyle&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DocRaptor Key:&lt;/strong&gt; To send the DocRaptor API request, you &lt;a href="https://docraptor.com/signup" rel="noopener noreferrer"&gt;need to subscribe to a plan on docraptor.com&lt;/a&gt;. With this, you get the API key that is required to authenticate with the REST service.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Typeset.sh Project Key &amp;amp; Project Token:&lt;/strong&gt; To send the request to the typeset.sh API, you &lt;a href="https://typeset.sh/en/register" rel="noopener noreferrer"&gt;need to subscribe to a plan on typeset.sh&lt;/a&gt;. With this, you get the project key and token required to authenticate with their REST service.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Local Command:&lt;/strong&gt; If you have a PDF rendering tool installed locally, you can provide the command here, be aware that the tool needs to support getting the HTML via STDIN and needs to return the PDF via STDOUT.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;AH Formatter Example:&lt;/em&gt; /usr/AHFormatterV71_64/run.sh -x 4 -d @STDIN -o @STDOUT&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Prince Example:&lt;/em&gt; prince — no-warn-css — javascript -&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;PDFreactor Example:&lt;/em&gt; cd /path/to/sh/script/ &amp;amp;&amp;amp; ./pdfreactor.sh (Get the script &lt;a href="https://gist.github.com/azettl/8546b17bd4880ce44c9b5b2b2ca596a9" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Change the Demo Theme and Add Multiple Themes
&lt;/h2&gt;

&lt;p&gt;All magazine themes are placed in the ‘wp-content/magazine_themes’ folder and can be directly edited there. If you prefer editing your theme directly in the WordPress UI, you can do so under ‘Appearance’ &amp;gt; ‘Magazine’.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F81ss3e7unimmlc3toec6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F81ss3e7unimmlc3toec6.png" alt="The Magazine Theme Editor" width="800" height="731"&gt;&lt;/a&gt;&lt;em&gt;The Magazine Theme Editor&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A magazine theme for PDF generation is using the following files. All of them are optional. If you use the magazine theme editor, all files will be generated even if you do not provide any content for some of them.&lt;/p&gt;

&lt;p&gt;Besides editing the theme, you can also duplicate, upload and download themes from this section in the WordPress backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  HTML
&lt;/h3&gt;

&lt;p&gt;In the HTML section of the magazine theme editor, you can edit all HTML files.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prefix (prefix.html):&lt;/strong&gt; The prefix HTML you can use for front covers or intros, basically anything which should be added only once at the beginning of the PDF. In the prefix, you can not use any placeholders.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Post (post.html):&lt;/strong&gt; The post HTML will be loaded whenever you want to render a Blogpost PDF; this file gets added once per selected Blogpost. So if you choose 5 Blogposts, this HTML gets repeated five times. Within the post HTML, you can use any placeholders, for example, the &lt;em&gt;&lt;code&gt;{{title}}&lt;/code&gt;&lt;/em&gt; to always show the current Blogpost title.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Page (page.html):&lt;/strong&gt; The page HTML will be loaded whenever you want to render a PDF from a page. This file gets added once per selected page. So if you choose five pages, this HTML gets repeated five times. Within the page HTML, you can use any placeholders, for example, the &lt;em&gt;&lt;code&gt;{{title}}&lt;/code&gt;&lt;/em&gt; to always show the current page title.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Postfix (postfix.html):&lt;/strong&gt; The postfix HTML you can use for back covers or indexes, basically anything which should be added only once at the end of the PDF. In the postfix, you can not use any placeholders.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can leave any of these HTML files empty if you do not need them. For example, if you do not need a cover, do not put content into the prefix HTML.&lt;/p&gt;

&lt;h3&gt;
  
  
  CSS
&lt;/h3&gt;

&lt;p&gt;In the magazine theme editor’s CSS section, you can edit all CSS files of any theme.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Main (style.css):&lt;/strong&gt; The main CSS is loaded for any rendering you do. In the main CSS, you can not use any placeholders.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Post (post.css):&lt;/strong&gt; The post CSS will be loaded whenever you want to render a Blogpost PDF; this file gets added once per selected Blogpost. So if you choose 5 Blogposts, this CSS gets repeated five times. Within the post CSS, you can use any placeholders, for example, the &lt;em&gt;&lt;code&gt;{{slug}}&lt;/code&gt;&lt;/em&gt; to get one class per Blogpost slug.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Page (page.css):&lt;/strong&gt; The page CSS will be loaded whenever you want to render a page PDF; this file gets added once per selected page. So if you choose five pages, this CSS gets repeated five times. Within the post CSS, you can use any placeholders, for example, the &lt;em&gt;&lt;code&gt;{{slug}}&lt;/code&gt;&lt;/em&gt; to get one class per page slug.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also leave any of these CSS files empty if you do not need them. For example, if you do not need a Blogpost specific CSS, do not put content into the post CSS file.&lt;/p&gt;

&lt;h3&gt;
  
  
  JavaScript
&lt;/h3&gt;

&lt;p&gt;In the magazine theme editor’s JavaScript section, you can edit all JavaScript files of your theme. &lt;strong&gt;Be aware that some rendering tools only support HTML and CSS.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Main (script.js):&lt;/strong&gt; The main JavaScript is loaded for any rendering you do. In the main JavaScript, you can not use any placeholders.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Post (post.js):&lt;/strong&gt; The post JavaScript will be loaded whenever you want to render a Blogpost PDF; this file gets added once per selected Blogpost. So if you choose 5 Blogposts, this JavaScript gets repeated five times. Within the post JavaScript, you can use any placeholders, for example, the &lt;em&gt;&lt;code&gt;{{slug}}&lt;/code&gt;&lt;/em&gt; to get one class per Blogpost slug.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Page (page.js):&lt;/strong&gt; The page JavaScript will be loaded whenever you want to render a page PDF; this file gets added once per selected page. So if you choose five pages, this JavaScript gets repeated five times. Within the post JavaScript, you can use any placeholders, for example, the &lt;em&gt;&lt;code&gt;{{slug}}&lt;/code&gt;&lt;/em&gt; to get one class per page slug.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can leave any of these JavaScript files empty if you do not need them. For example, if you do not need a Blogpost specific JavaScript, do not put content into the post JavaScript file.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Placeholders
&lt;/h3&gt;

&lt;p&gt;The placeholders &lt;em&gt;&lt;code&gt;{{slug}}&lt;/code&gt;&lt;/em&gt;, &lt;em&gt;&lt;code&gt;{{title}}&lt;/code&gt;&lt;/em&gt;, &lt;em&gt;&lt;code&gt;{{feature_image}}&lt;/code&gt;&lt;/em&gt; and &lt;em&gt;&lt;code&gt;{{content}}&lt;/code&gt;&lt;/em&gt; are for the post/page slug, title, feature image and content. Additionally you can use the placeholders &lt;em&gt;&lt;code&gt;{{author}}&lt;/code&gt;&lt;/em&gt;, &lt;em&gt;&lt;code&gt;{{date}}&lt;/code&gt;&lt;/em&gt;, &lt;em&gt;&lt;code&gt;{{date_gmt}}&lt;/code&gt;&lt;/em&gt;, &lt;em&gt;&lt;code&gt;{{excerpt}}&lt;/code&gt;&lt;/em&gt;, &lt;em&gt;&lt;code&gt;{{status}}&lt;/code&gt;&lt;/em&gt;. If you need to show the date of the post/page in a different format you can use the placeholders &lt;em&gt;&lt;code&gt;{{year}}&lt;/code&gt;&lt;/em&gt;, &lt;em&gt;&lt;code&gt;{{month}}&lt;/code&gt;&lt;/em&gt;, &lt;em&gt;&lt;code&gt;{{day}}&lt;/code&gt;&lt;/em&gt;, &lt;em&gt;&lt;code&gt;{{hour}}&lt;/code&gt;&lt;/em&gt;, &lt;em&gt;&lt;code&gt;{{minute}}&lt;/code&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please be aware that images need to be available via a public URL for the APIs to use them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ACF is also supported. Just add &lt;em&gt;&lt;code&gt;{{ACF_YOUR_FIELD_NAME}}&lt;/code&gt;&lt;/em&gt;. &lt;strong&gt;Important: use the name, not the label!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Widget
&lt;/h2&gt;

&lt;p&gt;If your WordPress theme support widgets, you can configure a simple PDF rendering widget for your frontend. The widget options are the title of your PDF rendering link and the magazine theme you want to render the PDF with. This way, you allow your users to generate PDFs anytime they want to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fdasvahk4w022es8wlkf8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdasvahk4w022es8wlkf8.png" alt="The Magazine Render PDF Widget with Text and Theme Options." width="800" height="374"&gt;&lt;/a&gt;&lt;em&gt;The Magazine Render PDF Widget with Text and Theme Options.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Shortcode
&lt;/h2&gt;

&lt;p&gt;You can also use a shortcode to display a link to render the PDF in any location of your post or page. &lt;/p&gt;

&lt;p&gt;The primary usage is just the tag and an attribute that defines the theme &lt;code&gt;[magazine theme="Demo"]&lt;/code&gt;. If you also want to change the text of the link, you can pass it with &lt;code&gt;[magazine theme="Demo" text="Click here for PDF Version"]&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backend and Bulk PDF Rendering
&lt;/h2&gt;

&lt;p&gt;When you want to render a PDF from one or multiple pages/blog posts, just navigate to the ‘All Posts’ or ‘All Pages’ sites in the backend. Select the pages/posts you want to have in your PDF and click on the ‘Bulk Actions’ dropdown. Depending on your magazine themes, you will see one or multiple entries like ‘Render PDF with Demo Theme’, select the theme you like and click on apply. Currently, the rendering process is always synchronous, so you will need to wait for the API/local tool to answer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fkmw7k9fezlyulk1jsl2k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fkmw7k9fezlyulk1jsl2k.png" alt="Bulk Actions for the three different Magazine Themes on my system." width="800" height="406"&gt;&lt;/a&gt;&lt;em&gt;Bulk Actions for the three different Magazine Themes on my system.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The resulting PDF is stored in the Media Library. It is also offered to you for downloading in a notification on the page where you started the rendering process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fuqdevjq3n7clt7fwnyrc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fuqdevjq3n7clt7fwnyrc.png" alt="Result Notification" width="800" height="170"&gt;&lt;/a&gt;&lt;em&gt;Result Notification&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;The plugin is currently in alpha status, and I am looking forward to your feedback and ideas on how to improve it.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Lastly, again the link for you to download the plugin: &lt;a href="https://gumroad.com/l/wp-magazine-printcss-cloud" rel="noopener noreferrer"&gt;Magazine (gumroad.com)&lt;/a&gt;.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>printcss</category>
      <category>plugin</category>
      <category>pdf</category>
    </item>
    <item>
      <title>PrintCSS: Running Headers and Footers</title>
      <dc:creator>Andreas</dc:creator>
      <pubDate>Sat, 27 Feb 2021 23:19:20 +0000</pubDate>
      <link>https://dev.to/azettl/printcss-running-headers-and-footers-2080</link>
      <guid>https://dev.to/azettl/printcss-running-headers-and-footers-2080</guid>
      <description>&lt;p&gt;If you want to use HTML inside the page margin boxes, you will need to use running elements. These elements are normal HTML nodes with the CSS position property set to ‘running(NAME)’.&lt;/p&gt;

&lt;p&gt;The name given to the running element is used within the page margin boxes to set their content to the running elements inner HTML.&lt;/p&gt;

&lt;p&gt;To do so, you need to set the specific &lt;a href="https://www.w3.org/TR/css-page-3/#margin-boxes" rel="noopener noreferrer"&gt;page margin box’s&lt;/a&gt; CSS content property to ‘element(NAME)’, where NAME is the given running element name.&lt;/p&gt;

&lt;p&gt;Elements with the position running do not appear in the document until you assign them to some CSS content property.&lt;/p&gt;

&lt;p&gt;Your running elements must come first in your HTML structure. For example, if you put the footer element before the closing body tag, the running footer will only appear on the last page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample Header
&lt;/h2&gt;

&lt;p&gt;So after reading the theory, let’s jump right in and create a small example. We will define a running header with a simple text and a footer with HTML content.&lt;/p&gt;

&lt;p&gt;In the body of our document, let’s define our two header div elements.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="headerLeft"&amp;gt;
  Running Headers and Footers
&amp;lt;/div&amp;gt;

&amp;lt;div class="headerRight"&amp;gt;
  A sample document
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As mentioned above, we now need to define the position as ‘running’, and we will also give our headers some basic style.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.headerLeft{
  position: running(headerLeft);
  font-size:12pt;
}

.headerRight{
  position: running(headerRight);
  font-size:8pt;
  font-style: italic;
  text-align: right;
  color:#667eea;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Before we render the PDF, we need to define the &lt;a href="https://www.w3.org/TR/css-page-3/#margin-boxes" rel="noopener noreferrer"&gt;page margin boxes&lt;/a&gt; and the page in CSS.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page  {
  size: A6;
  margin: 20mm;

  @top-left{
    content: element(headerLeft);
    border-bottom:2px solid #434190;
  }

  @top-center{
    border-bottom:2px solid #434190;
  }

  @top-right{
    content: element(headerRight);
    border-bottom:2px solid #434190;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see, the page size is A6, and we have a margin of 2cm for all sides of the page. Our running header elements are used in the ‘&lt;a href="https://www.w3.org/TR/css-page-3/#at-ruledef-top-left" rel="noopener noreferrer"&gt;@top-left&lt;/a&gt;’ and ‘&lt;a href="https://www.w3.org/TR/css-page-3/#at-ruledef-top-right" rel="noopener noreferrer"&gt;@top-right&lt;/a&gt;’ definition. Additionally, I added a bottom border to all top margin boxes.&lt;/p&gt;

&lt;p&gt;The rendering result always depends on the tool you are using. Here are two samples with &lt;a href="https://www.pdfreactor.com/" rel="noopener noreferrer"&gt;PDFreactor&lt;/a&gt; and &lt;a href="https://www.princexml.com/" rel="noopener noreferrer"&gt;Prince&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fh0d65mcy8pofow0n102d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fh0d65mcy8pofow0n102d.png" alt="Result with Prince" width="789" height="338"&gt;&lt;/a&gt;&lt;em&gt;Result with Prince&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fcdllbgxhcq96hnhirl0z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fcdllbgxhcq96hnhirl0z.png" alt="PDFreactor Result" width="783" height="288"&gt;&lt;/a&gt;&lt;em&gt;PDFreactor Result&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;More tools you can check on &lt;a href="https://printcss.live/" rel="noopener noreferrer"&gt;printcss.live&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sample Footer
&lt;/h3&gt;

&lt;p&gt;Now, as we have the header ready, let’s have a look at the footer. I want to put a logo on the left side and contact information on the right in the footer. Between both, I want to add the current and total page numbers.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="footerLeft"&amp;gt;
  &amp;lt;img src="https://printcss.live/img/logo.png" /&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class="footerRight"&amp;gt;
  &amp;lt;a href="mailto:info@azettl.net"&amp;gt;info@azettl.net&amp;lt;/a&amp;gt;
  &amp;lt;br /&amp;gt;
  &amp;lt;a href="https://printcss.live/"&amp;gt;printcss.live&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In the CSS, we will give the footer classes a position ‘running’ again and some basic style like the logo’s width.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.footerLeft{
  position: running(footerLeft);
}

.footerLeft img{
  width:20mm;
}

.footerRight{
  position: running(footerRight);
  text-align: right;
  font-size:8pt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If we render now, we will not see the elements as we still need to assign them to one of the &lt;a href="https://www.w3.org/TR/css-page-3/#margin-boxes" rel="noopener noreferrer"&gt;page margin boxes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fj05vk1ajxvpaa310og8g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fj05vk1ajxvpaa310og8g.png" alt="Result with Prince" width="786" height="1030"&gt;&lt;/a&gt;&lt;em&gt;Result with Prince&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We need to define our bottom margin boxes inside the &lt;a href="https://www.w3.org/TR/css-page-3/#at-page-rule" rel="noopener noreferrer"&gt;‘@page’ rule&lt;/a&gt; to make this work.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@bottom-right{
  content: element(footerRight);
  border-top:2px solid #434190;
}

@bottom-center{
  content: counter(page) ' / ' counter(pages);
  border-top:2px solid #434190;
  font-size:8pt;
}

@bottom-left{
  content: element(footerLeft);
  border-top:2px solid #434190;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Again I added some border, this time on the top. Also, you can see in the bottom center margin box I define the content as “counter(page) ‘ / ‘ counter(pages)”. The &lt;a href="https://www.w3.org/TR/css-page-3/#page-based-counters" rel="noopener noreferrer"&gt;counter ‘page’&lt;/a&gt; returns us the current page number, while the &lt;a href="https://www.w3.org/TR/css-page-3/#page-based-counters" rel="noopener noreferrer"&gt;‘pages’ counter&lt;/a&gt; gives us the total number of pages.&lt;/p&gt;

&lt;p&gt;Now our document has a nice header and footer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fqvzhz5lrew3ie6hfnd8l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fqvzhz5lrew3ie6hfnd8l.png" alt="Running header and footer rendered with PDFreactor" width="800" height="1001"&gt;&lt;/a&gt;&lt;em&gt;Running header and footer rendered with PDFreactor&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Complete Sample
&lt;/h3&gt;

&lt;p&gt;To see whether our running header and footer elements actually work, we need to add some content. If our code works fine we will see the header and footer on all pages.&lt;/p&gt;

&lt;p&gt;The content should be placed inside the body element but after all running header and footer elements.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="content"&amp;gt;
  &amp;lt;h1&amp;gt;PrintCSS: Running Headers and Footers&amp;lt;/h1&amp;gt;

  &amp;lt;p&amp;gt;
    If you want to use HTML inside the page margin boxes, you will need to use running elements. These elements are normal HTML nodes with the CSS position property set to 'running(NAME)'.
  &amp;lt;/p&amp;gt;

  &amp;lt;p&amp;gt;
    The name given to the running element is used within the page margin boxes to set their content to the running elements inner HTML.
  &amp;lt;/p&amp;gt;

  &amp;lt;p&amp;gt;
    To do so, you need to set the specific page margin box's CSS content property to 'element(NAME)', where NAME is the given running element name.
  &amp;lt;/p&amp;gt;

  &amp;lt;p&amp;gt;
    Elements with the position running do not appear in the document until you assign them to some CSS content property.
  &amp;lt;/p&amp;gt;

  &amp;lt;p&amp;gt;
    Your running elements must come first in your HTML structure. For example, if you put the footer element before the closing body tag, the running footer will only appear on the last page.
  &amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you render on &lt;a href="https://printcss.live/" rel="noopener noreferrer"&gt;printcss.live&lt;/a&gt; now, you will see the running elements on all pages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fcustiogzx5qqpwv3dmca.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fcustiogzx5qqpwv3dmca.png" alt="Rendering result with PDFreactor." width="800" height="548"&gt;&lt;/a&gt;&lt;em&gt;Rendering result with PDFreactor.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>html</category>
      <category>css</category>
      <category>headers</category>
      <category>footers</category>
    </item>
    <item>
      <title>PrintCSS: Table of Contents</title>
      <dc:creator>Andreas</dc:creator>
      <pubDate>Sat, 27 Feb 2021 23:16:35 +0000</pubDate>
      <link>https://dev.to/azettl/printcss-table-of-contents-gh4</link>
      <guid>https://dev.to/azettl/printcss-table-of-contents-gh4</guid>
      <description>&lt;p&gt;A widespread use case for your PrintCSS document is creating a table of contents. But how would you know the page numbers of your chapters before you render the document? The content takes as many pages as it needs, and if your content is dynamic, it is impossible to say how many pages a chapter would result in.&lt;/p&gt;

&lt;p&gt;Before we start with our table of contents, we will need some content to create the table. So let’s take the result from my previous article &lt;a href="https://medium.com/@azettl/printcss-running-headers-and-footers-3bef60a60d62" rel="noopener noreferrer"&gt;PrintCSS: Running Headers and Footers&lt;/a&gt;, and extend the content.&lt;/p&gt;

&lt;p&gt;Until now, the content has only one chapter. So we will add “PrintCSS: Table of Contents” with the text above as the second chapter.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;h1&amp;gt;PrintCSS: Table of Contents&amp;lt;/h1&amp;gt;

&amp;lt;p&amp;gt;A very common use case for your PrintCSS document is creating a table of contents. But how would you know the page numbers of your chapters before you render the document? The content takes as many pages as it needs and if your content is dynamic it is impossible to say how many pages a chapter would result in.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Before we start with our table of contents we will need some content to create the table for. So let's take the result from my previous article PrintCSS: Running Headers and Footers and extend the content.&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This HTML needs to be added inside the div with the class ‘content’.&lt;/p&gt;

&lt;p&gt;To separate the chapters, we use the CSS property ‘&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/page-break-before" rel="noopener noreferrer"&gt;page-break-before&lt;/a&gt;’ (the new version ‘break-before’ does not work with Prince yet) with the ‘always’ value. This will start each chapter on a new page.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.content h1{
  page-break-before: always;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can see now that the first page of our PDF is blank, and each chapter starts on a new page. If you want to try it out yourself you can render the code on &lt;a href="https://printcss.live/" rel="noopener noreferrer"&gt;printcss.live&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fneclplo8weppkv14onns.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fneclplo8weppkv14onns.png" alt="Rendering Result with Prince, showing the first page empty." width="800" height="570"&gt;&lt;/a&gt;&lt;em&gt;Rendering Result with Prince, showing the first page empty.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F38zdi696dlgo7i05o3ex.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F38zdi696dlgo7i05o3ex.png" alt="The new second chapter, rendered with Prince" width="800" height="572"&gt;&lt;/a&gt;&lt;em&gt;The new second chapter, rendered with Prince&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The first blank page we will use for our table of contents. In the HTML, we add a new div element with the class ‘toc’ and add the links to our chapters.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="toc"&amp;gt;
  &amp;lt;h1&amp;gt;Table of contents&amp;lt;/h1&amp;gt;
  &amp;lt;a href="#running"&amp;gt;PrintCSS: Running Headers and Footers&amp;lt;/a&amp;gt;
  &amp;lt;a href="#toc"&amp;gt;PrintCSS: Table of Contents&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you see, we will use anchors to link the table of contents elements to the chapters. This means that each chapter heading needs an ‘id’ attribute.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;h1 id="running"&amp;gt;PrintCSS: Running Headers and Footers&amp;lt;/h1&amp;gt;

...

&amp;lt;h1 id="toc"&amp;gt;PrintCSS: Table of Contents&amp;lt;/h1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Faotegcuerjoefir596di.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Faotegcuerjoefir596di.png" alt="Links in the PDF with HTML anchors, rendered in Prince." width="554" height="763"&gt;&lt;/a&gt;&lt;em&gt;Links in the PDF with HTML anchors, rendered in Prince.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you render now and click on one of the table of contents elements, it will already direct you to the correct chapter. But we are missing the page numbers. For that, we can use a CSS function called ‘&lt;a href="https://www.w3.org/TR/css-gcpm-3/#target-counter" rel="noopener noreferrer"&gt;target-counter&lt;/a&gt;’.&lt;/p&gt;

&lt;p&gt;The first parameter for the ‘&lt;a href="https://www.w3.org/TR/css-gcpm-3/#target-counter" rel="noopener noreferrer"&gt;target-counter&lt;/a&gt;’ is the target URL in our case ‘attr(href)’. The second parameter is the counter; as we want the page number, it is ‘page’.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.toc a{
  display:block;
}

.toc a::after {
  content: target-counter(attr(href), page);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fqfw35wxmdsdixdbdwkzt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fqfw35wxmdsdixdbdwkzt.png" alt="Result of target-counter, rendered with Prince." width="362" height="234"&gt;&lt;/a&gt;&lt;em&gt;Result of target-counter, rendered with Prince.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To make it look better, let’s add dot leaders with the ‘&lt;a href="https://www.w3.org/TR/css-gcpm-3/#funcdef-leader" rel="noopener noreferrer"&gt;leader&lt;/a&gt;’ function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ftoafhddtguacq5xdrmv1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ftoafhddtguacq5xdrmv1.png" alt="Final result with the leader function, rendered with Prince." width="800" height="569"&gt;&lt;/a&gt;&lt;em&gt;Final result with the leader function, rendered with Prince.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>html</category>
      <category>css</category>
      <category>printcss</category>
      <category>toc</category>
    </item>
    <item>
      <title>PrintCSS: Page Margin Boxes</title>
      <dc:creator>Andreas</dc:creator>
      <pubDate>Sat, 27 Feb 2021 23:15:02 +0000</pubDate>
      <link>https://dev.to/azettl/printcss-page-margin-boxes-2pac</link>
      <guid>https://dev.to/azettl/printcss-page-margin-boxes-2pac</guid>
      <description>&lt;p&gt;In my article about running elements like headers, we already worked with page margin boxes without explaining them in detail.&lt;/p&gt;

&lt;p&gt;So what are &lt;a href="https://www.w3.org/TR/css-page-3/#margin-boxes" rel="noopener noreferrer"&gt;page margin boxes&lt;/a&gt;? They are boxes within the page margin and can contain generated content, like page numbers. Also, they are often used to add the document title or copyright information on many pages.&lt;/p&gt;

&lt;p&gt;There are 16 page margin boxes so let us have a look at every single box. We will go clockwise, starting with the top left corner.&lt;/p&gt;

&lt;p&gt;If you want to try the samples yourself, head over to &lt;a href="https://printcss.live/" rel="noopener noreferrer"&gt;printcss.live&lt;/a&gt; and play around with different PDF rendering tools.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fuo8sjmy38h2irnceicwe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fuo8sjmy38h2irnceicwe.png" alt="top-left-corner page margin box, rendered with typeset.sh" width="353" height="204"&gt;&lt;/a&gt;&lt;em&gt;top-left-corner page margin box, rendered with typeset.sh&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  @top-left-corner (1)
&lt;/h3&gt;

&lt;p&gt;The top left corner’s size is defined by the top and left margin of the page. In the screenshot on the left, you see the corner on a page with a margin of 20mm. This means our box will also be 20x20mm. If you defined the page’s left margin as 10mm and the top margin to be 20mm, the top left corner would have a size of 10x20mm.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page  {
 margin: 20mm;

 @top-left-corner{
  content:"1";
  background-color:#1a202c;
  color:#fff;
  text-align:center;
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  @top-left (2)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F1x3ez7tkvlofbe0bc59e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F1x3ez7tkvlofbe0bc59e.png" alt="top-left page margin box, rendered with typeset.sh" width="623" height="166"&gt;&lt;/a&gt;&lt;em&gt;top-left page margin box, rendered with typeset.sh&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The top left margin box sits between the top left corner and the top center box. The width of this box is variable. As you see in the screenshot above, it goes all the way to the top right corner page margin box. Once we define the top center box, the width will decrease as the center box also takes up some space.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page  {
 margin: 20mm;

 /* previous css code... */

 @top-left{
  content:"2";
  background-color:#742a2a;
  color:#fff;
  text-align:center;
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  @top-center (3)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fu30bbcczyzmwp1ew18d0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fu30bbcczyzmwp1ew18d0.png" alt="top-center page margin box, rendered with typeset.sh" width="603" height="171"&gt;&lt;/a&gt;&lt;em&gt;top-center page margin box, rendered with typeset.sh&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The top center is, as the name suggests, in the center of the top margin boxes. This box also has a variable width. The width depends on its neighbors’ width, the top left, and top right page margin boxes.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page  {
 margin: 20mm;

 /* previous css code... */

 @top-center{
  content:"3";
  background-color:#7b341e;
  color:#fff;
  text-align:center;
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  @top-right (4)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fht4ng0js44h51acjxp3u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fht4ng0js44h51acjxp3u.png" alt="top-right page margin box, rendered with typeset.sh" width="601" height="184"&gt;&lt;/a&gt;&lt;em&gt;top-right page margin box, rendered with typeset.sh&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The top right margin box sits between the top center and the top right corner box. The width of this box is variable. As you see in the screenshot above, it goes from the center box to the top right corner page margin box.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page  {
 margin: 20mm;

 /* previous css code... */

 @top-right{
  content:"4";
  background-color:#744210;
  color:#fff;
  text-align:center;
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ffxp9odq7n50e5c1133gt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ffxp9odq7n50e5c1133gt.png" alt="top right corner, rendered with typeset.sh" width="242" height="156"&gt;&lt;/a&gt;&lt;em&gt;top right corner, rendered with typeset.sh&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  @top-right-corner (5)
&lt;/h3&gt;

&lt;p&gt;The top right corner box size is defined by the top and right margin of the page. Look at the definition of the top left corner for details.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page  {
 margin: 20mm;

 /* previous css code... */

 @top-right-corner{
  content:"5";
  background-color:#22543d;
  color:#fff;
  text-align:center;
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  @right-top (6)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fa8a6pgre0jwi9ouc8vey.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fa8a6pgre0jwi9ouc8vey.png" alt="right top page margin box, rendered with typeset.sh" width="399" height="551"&gt;&lt;/a&gt;&lt;em&gt;right top page margin box, rendered with typeset.sh&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The right top page margin box has a variable height. It fills all the space between the top right corner and the right middle box. As we have not defined the right middle box yet, this box goes till the bottom right corner box.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page  {
 margin: 20mm;

 /* previous css code... */

 @right-top{
  content:"6";
  background-color:#234e52;
  color:#fff;
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  @right-middle (7)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F8pbkl4p4098fkwnlejhr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F8pbkl4p4098fkwnlejhr.png" alt="The right middle page margin box, rendered with typeset.sh" width="598" height="833"&gt;&lt;/a&gt;&lt;em&gt;The right middle page margin box, rendered with typeset.sh&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The right middle box is vertically centered on the right side. Depending on its height, the neighboring boxes (right top and right bottom) adjust.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page  {
 margin: 20mm;

 /* previous css code... */

 @right-middle{
  content:"7";
  background-color:#2a4365;
  color:#fff;
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  @right-bottom (8)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fymnr5thck9ji2leljrzf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fymnr5thck9ji2leljrzf.png" alt="The right bottom margin box (8), rendered with typeset.sh" width="588" height="823"&gt;&lt;/a&gt;&lt;em&gt;The right bottom margin box (8), rendered with typeset.sh&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The right bottom page margin box behaves the same as the right top box, just on the page’s bottom. It fills the space between the right middle box and the right bottom corner box.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page  {
 margin: 20mm;

 /* previous css code... */

 @right-bottom{
  content:"8";
  background-color:#3c366b;
  color:#fff;
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  @bottom-right-corner, @bottom-right, @bottom-center, @bottom-left, @bottom-left-corner (9–13)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fdlvxgmqa7s5jrvrct5kq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdlvxgmqa7s5jrvrct5kq.png" alt="The bottom page margin boxes, rendered with typeset.sh" width="589" height="822"&gt;&lt;/a&gt;&lt;em&gt;The bottom page margin boxes, rendered with typeset.sh&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The bottom page margin boxes behave the same way as the top page margin boxes. For details, please have a look at the top boxes above.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page  {
 margin: 20mm;

 /* previous css code... */

 @bottom-right-corner {
  content:"9";
  background-color:#44337a;
  color:#fff;
  text-align:center;
 }

 @bottom-right {
  content:"10";
  background-color:#702459;
  color:#fff;
  text-align:center;
 }

 @bottom-center{
  content:"11";
  background-color:#1a202c;
  color:#fff;
  text-align:center;
 }

 @bottom-left{
  content:"12";
  background-color:#742a2a;
  color:#fff;
  text-align:center;
 }

 @bottom-left-corner{
  content:"13";
  background-color:#7b341e;
  color:#fff;
  text-align:center;
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  @left-bottom, @left-middle, @left-top (14–16)
&lt;/h3&gt;

&lt;p&gt;For the left page margin boxes, have a look at the right boxes. They behave the same way just on the other side.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fh6b1a0r7j68v5k1l82xv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fh6b1a0r7j68v5k1l82xv.png" alt="All page margin boxes on one page, rendered with typeset.sh" width="587" height="823"&gt;&lt;/a&gt;&lt;em&gt;All page margin boxes on one page, rendered with typeset.sh&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page  {
 margin: 20mm;

 /* previous css code... */

 @left-bottom{
  content:"14";
  background-color:#744210;
  color:#fff;
 }

 @left-middle{
  content:"15";
  background-color:#22543d;
  color:#fff;
 }

 @left-top{
  content:"16";
  background-color:#234e52;
  color:#fff;
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>html</category>
      <category>css</category>
      <category>printcss</category>
      <category>margin</category>
    </item>
    <item>
      <title>PrintCSS: Footnotes</title>
      <dc:creator>Andreas</dc:creator>
      <pubDate>Sat, 27 Feb 2021 23:12:33 +0000</pubDate>
      <link>https://dev.to/azettl/printcss-footnotes-30c4</link>
      <guid>https://dev.to/azettl/printcss-footnotes-30c4</guid>
      <description>&lt;p&gt;Footnotes play an important role when it comes to creating books or PDF documents in general. Unfortunately, not all PDF rendering tools are supporting the W3C draft yet. While the commercial tools all have a good integration of the draft, most open-source tools lack support for footnotes completely.&lt;/p&gt;

&lt;p&gt;Still, in this article, I will explain the &lt;a href="https://www.w3.org/TR/css-gcpm-3/#footnotes" rel="noopener noreferrer"&gt;W3C footnote draft&lt;/a&gt; to you, and we will see the rendering result with the different tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Terminology
&lt;/h3&gt;

&lt;p&gt;Before we go through some samples, we should first get to know the different terms used.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fvkhy78l4zhvb9bmquttb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fvkhy78l4zhvb9bmquttb.png" alt="Footnotes rendered with PDFreactor." width="590" height="825"&gt;&lt;/a&gt;&lt;em&gt;Footnotes rendered with PDFreactor.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A footnote call is a number or sometimes a symbol in the main text pointing to the footnote’s body.&lt;/p&gt;

&lt;p&gt;The footnote body consists of the footnote marker, which is actually the number or symbol from the footnote call. The marker can have extra punctuation. After the marker, we have the footnote element, which is still part of the footnote body. The footnote element is the actual content of the footnote. The content is defined within the text flow but gets removed from there and is shown as a footnote.&lt;/p&gt;

&lt;p&gt;All your footnotes are in the footnote area, so the area of the page where your footnotes are displayed. Above that area, you can have a horizontal rule, which is called the footnote rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  From Terminology to CSS and HTML
&lt;/h3&gt;

&lt;p&gt;If you want to try the samples yourself, head over to &lt;a href="https://printcss.live/" rel="noopener noreferrer"&gt;printcss.live&lt;/a&gt; and play around with different PDF rendering tools.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page{
 size: A6;

 @footnote {
  /* The Background color highlights the footnote area */
  background-color:#feb2b2;

  /* The Border Top is the footnote rule */
  border-top:5px solid #38a169;
 }
}

/* This element is for a single footnote body */
.footnote { 
 float: footnote;
 background-color:#a3bffa; 
 margin-bottom:2mm;
}

/* As the name says this is the footnote call */
.footnote::footnote-call { 
 background-color:#d6bcfa;
}

/* And the footnote marker */
.footnote::footnote-marker { 
 background-color:#fbb6ce;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In the HTML document, all you need to do is add an element with the footnote class at the desired place.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;p&amp;gt;
    Footnotes play an important role when it comes to creating books or PDF documents in general. Unfortunately, not all PDF rendering tools are supporting the W3C draft&amp;lt;span class="footnote"&amp;gt;Sample Footnote Content&amp;lt;/span&amp;gt; yet. While the commercial tools all have a good integration of the draft, most open-source&amp;lt;span class="footnote"&amp;gt;Another Sample Footnote Content&amp;lt;/span&amp;gt; tools lack support for footnotes completely.
&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The result of the above code, with some extra content, header and footer looks like the following.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fn9qh9n7gbubfvcgzexur.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fn9qh9n7gbubfvcgzexur.png" alt="Footnotes rendered with PDFreactor and Prince." width="800" height="556"&gt;&lt;/a&gt;&lt;em&gt;Footnotes rendered with PDFreactor and Prince.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As I mentioned in the beginning, most open-source tools lack the support of footnotes until now. So let us have a look at how the same code renders with Weasyprint and Paged.js.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fcan5cgoaxr02bd76tn44.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fcan5cgoaxr02bd76tn44.png" alt="No footnote support with Weasyprint and Paged.js" width="800" height="552"&gt;&lt;/a&gt;&lt;em&gt;No footnote support with Weasyprint and Paged.js&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Footnote Marker &amp;amp; Call
&lt;/h3&gt;

&lt;p&gt;Prince also supports symbols besides the decimal numbers. So the following CSS code will work only for Prince.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.footnote::footnote-call {
  background-color:#d6bcfa;
  content: counter(footnote, symbols('*', '&amp;amp;', '%', '&amp;gt;'));
}
.footnote::footnote-marker { /* And the footnote marker */
  background-color:#fbb6ce;
  content: counter(footnote, symbols('*', '&amp;amp;', '%', '&amp;gt;'));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F1ic9ame5desngacixfdx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F1ic9ame5desngacixfdx.png" alt="Footnote Marker &amp;amp; Call Symbols with Prince" width="590" height="823"&gt;&lt;/a&gt;&lt;em&gt;Footnote Marker &amp;amp; Call Symbols with Prince&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But both rendering tools support the values allowed for the CSS property ‘&lt;a href="https://www.w3schools.com/cssref/pr_list-style-type.asp" rel="noopener noreferrer"&gt;list-style-type&lt;/a&gt;’, for example, ‘upper-greek’. This has nothing to do with footnotes directly but with the ‘counter’ function, which is used here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fmqtp7lkzf4h2vq8aljuj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fmqtp7lkzf4h2vq8aljuj.png" alt="‘upper-greek’ example in Prince and PDFreactor" width="800" height="555"&gt;&lt;/a&gt;&lt;em&gt;‘upper-greek’ example in Prince and PDFreactor&lt;/em&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.footnote::footnote-call {
  background-color:#d6bcfa;
  content: counter(footnote, lower-greek);
 }

.footnote::footnote-marker {
  background-color:#fbb6ce;
  content: counter(footnote, lower-greek);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>html</category>
      <category>css</category>
      <category>printcss</category>
      <category>footnotes</category>
    </item>
    <item>
      <title>PrintCSS: Recreating a eBook Cover</title>
      <dc:creator>Andreas</dc:creator>
      <pubDate>Sat, 27 Feb 2021 23:10:38 +0000</pubDate>
      <link>https://dev.to/azettl/printcss-recreating-a-ebook-cover-16kf</link>
      <guid>https://dev.to/azettl/printcss-recreating-a-ebook-cover-16kf</guid>
      <description>&lt;p&gt;In this article, we will not go into specifics of CSS paged media. Instead, let us have some fun today and try to recreate the eBook cover from Florin Pop’s first eBook.&lt;/p&gt;

&lt;p&gt;The goal is to get as close as possible to the original cover. If you want to try the below code yourself, you can install a PDF generation tool or head over to &lt;a href="https://printcss.live/" rel="noopener noreferrer"&gt;printcss.live&lt;/a&gt;. This website allows you to try different rendering tools in the browser.&lt;/p&gt;

&lt;p&gt;Enough now with the boring intro! Let us start!&lt;/p&gt;

&lt;h2&gt;
  
  
  Ten++ Ways to Make Money as a Developer by Florin Pop
&lt;/h2&gt;

&lt;p&gt;The eBook is not released yet, but you can find the cover and details about the book on &lt;a href="https://gumroad.com/l/moneydev" rel="noopener noreferrer"&gt;Gumroad&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2010%2F1%2AG6dNT5VPq3nspv8bLndLLg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2010%2F1%2AG6dNT5VPq3nspv8bLndLLg.jpeg" alt="[Ten++ Ways to Make Money as a Developer](https://gumroad.com/l/moneydev) by Florin Pop" width="800" height="525"&gt;&lt;/a&gt;&lt;em&gt;&lt;a href="https://gumroad.com/l/moneydev" rel="noopener noreferrer"&gt;Ten++ Ways to Make Money as a Developer&lt;/a&gt; by Florin Pop&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The HTML Part
&lt;/h3&gt;

&lt;p&gt;Gladly Florin did not go super crazy with the book cover layout, so our HTML should be pretty basic. The only thing we leave out from the layout is the picture of the pig.&lt;/p&gt;

&lt;p&gt;Yeah, I know this is a major part, and I also found the picture on some websites. The thing is, I do not want to copy it from somewhere and did not find it on a stock photo site.&lt;/p&gt;

&lt;p&gt;Instead, we will put a picture from Bill Murray via the amazing &lt;a href="https://www.fillmurray.com/" rel="noopener noreferrer"&gt;fillmurray.com &lt;/a&gt;image placeholder website.&lt;/p&gt;

&lt;p&gt;So our HTML will look like the following.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="purple"&amp;gt;&amp;amp;#x3C;h1&amp;amp;#x3E;&amp;lt;/div&amp;gt;
&amp;lt;div class="title"&amp;gt;
  Ten&amp;lt;span class="purple"&amp;gt;++&amp;lt;/span&amp;gt; Ways to Make Money as a Developer
&amp;lt;/div&amp;gt;
&amp;lt;div class="purple"&amp;gt;&amp;amp;#x3C;/h1&amp;amp;#x3E;&amp;lt;/div&amp;gt;
&amp;lt;div class="author"&amp;gt;Florin Pop&amp;lt;/div&amp;gt;
&amp;lt;img src="[https://www.fillmurray.com/400/400](https://www.fillmurray.com/400/400)" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Rendering only the HTML code will result in a pretty basic PDF page. Be aware that you might get a different image of Bill Murray.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F0638kjl0mof5veu26cm6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F0638kjl0mof5veu26cm6.png" alt="Florins eBook Cover without CSS, rendered with Weasyprint" width="597" height="827"&gt;&lt;/a&gt;&lt;em&gt;Florins eBook Cover without CSS, rendered with Weasyprint&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fun/CSS Part
&lt;/h3&gt;

&lt;p&gt;For this and also all the other book covers let us assume a page size of 8.27 inch by 11.69 inch (&lt;a href="http://www.printernational.org/iso-paper-sizes.php" rel="noopener noreferrer"&gt;ISO Code A4&lt;/a&gt;&lt;strong&gt;).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As we all know, Florin’s favorite color is green. Kidding, it’s purple, so let us start with the purple parts. The left part of the cover is completely purple, and we can use the &lt;a href="https://medium.com/printcss/printcss-page-margin-boxes-9b89d3428cf5" rel="noopener noreferrer"&gt;page margin boxes&lt;/a&gt; to achieve the same.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fpalczojflbqrf1tipzed.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fpalczojflbqrf1tipzed.png" alt="Florins eBook Cover with some CSS, rendered with Weasyprint" width="593" height="822"&gt;&lt;/a&gt;&lt;em&gt;Florins eBook Cover with some CSS, rendered with Weasyprint&lt;/em&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page{
   size:8.27in 11.69in; 
   margin:0.39in;

   @top-left-corner{
       content:"";
       background-color:#663399;
   }

   @left-top{
       content:"";
       background-color:#663399;
   }

   @left-middle{
       content:"";
       background-color:#663399;
   }

   @left-bottom{
       content:"";
       background-color:#663399;
   }

   @bottom-left-corner{
       content:"";
       background-color:#663399;
   }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In the next step, let us style the title of the book. First, we will need to redo our HTML element for the title. Instead of one div, let’s split it into multiple divs to achieve the desired line breaks.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="title"&amp;gt;
  Ten&amp;lt;span class="purple"&amp;gt;++&amp;lt;/span&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class="title"&amp;gt;
  Ways
&amp;lt;/div&amp;gt;
&amp;lt;div class="title"&amp;gt;
  to Make Money
&amp;lt;/div&amp;gt;
&amp;lt;div class="title"&amp;gt;
  as a
&amp;lt;/div&amp;gt;
&amp;lt;div class="title"&amp;gt;
  Developer
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now we need to get the title’s styles right, which means guessing the font and font size.&lt;/p&gt;

&lt;p&gt;As I am terrible at guessing fonts, let us take &lt;a href="https://fonts.google.com/specimen/Roboto" rel="noopener noreferrer"&gt;Roboto from Google Fonts&lt;/a&gt;, and let’s pick a font size of 74pt. For the H1 tag and the ‘++’, we will use a font size of 32pt.&lt;/p&gt;

&lt;p&gt;All our divs will also get a margin-left, so they are not starting at the purple stripe we added on the left.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@500;900&amp;amp;display=swap');


body{
   font-family: 'Roboto', sans-serif;
}

div{
   margin-left:0.78in;
}

.purple{
   color:#663399;
   font-size:32pt;
}

.title{
   font-size:74pt;
   font-weight:bold;
   text-transform:uppercase;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In the code above, you can see that I also added the ‘text-transform’ property, so the title looks like the one on Florin’s eBook.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fgqy2xmlvcnicdllm4nuk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fgqy2xmlvcnicdllm4nuk.png" alt="The current progress of our title, rendered with Weasyprint" width="584" height="819"&gt;&lt;/a&gt;&lt;em&gt;The current progress of our title, rendered with Weasyprint&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the image above, you also see that the image of Bill Murray disappeared. Actually, it is now on the second page of our PDF as it is not fitting anymore due to the font size change.&lt;/p&gt;

&lt;p&gt;Before we take care of the image, let us first fix the author’s name and the ‘++’.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.title .purple{
   position:relative;
   top:-16pt;
   left:16pt;
   letter-spacing:4pt;
}

.author{
   margin-top:1.39in;
   font-size:26pt;
   font-weight:bold;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F85k6qw3szkbyqy8a9upe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F85k6qw3szkbyqy8a9upe.png" alt="eBook Cover with Author and ‘++’ positioned correctly and rendered with Weasyprint" width="596" height="825"&gt;&lt;/a&gt;&lt;em&gt;eBook Cover with Author and ‘++’ positioned correctly and rendered with Weasyprint&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the end, let us bring the image back to the first page and position it in the right bottom corner.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;img{
   position:absolute;
   bottom:0;
   right:0.78in;
   max-width:2.5in;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So here it is our recreation of Florin’s book cover! Not a 100 percent the same, but I am pretty happy about the result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ffxqp70uwcubogkgotcvl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ffxqp70uwcubogkgotcvl.png" alt="The final result rendered with Weasyprint" width="594" height="822"&gt;&lt;/a&gt;&lt;em&gt;The final result rendered with Weasyprint&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Oh, those renderers!
&lt;/h2&gt;

&lt;p&gt;What you need to be aware of, all the samples you see above were rendered with Weasyprint. With all these PDF generation tools, it is a little bit like in the old days of web development. Different tools give you different results. For that, I rendered the same with four different tools, and as you can see below, we get four totally different PDFs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fvslr7bdf79arm49sv38c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fvslr7bdf79arm49sv38c.png" alt="The code from this article rendered with four different tools" width="800" height="1113"&gt;&lt;/a&gt;&lt;em&gt;The code from this article rendered with four different tools&lt;/em&gt;&lt;/p&gt;

</description>
      <category>html</category>
      <category>css</category>
      <category>printcss</category>
      <category>ebook</category>
    </item>
    <item>
      <title>PrintCSS: Counter and Cross References</title>
      <dc:creator>Andreas</dc:creator>
      <pubDate>Sat, 27 Feb 2021 23:08:33 +0000</pubDate>
      <link>https://dev.to/azettl/printcss-counter-and-cross-references-h2k</link>
      <guid>https://dev.to/azettl/printcss-counter-and-cross-references-h2k</guid>
      <description>&lt;p&gt;In this article, I want to explain how counters work and how to do cross-references in your document. Both counter and also cross-references were already a topic in the article about the table of contents.&lt;/p&gt;

&lt;p&gt;There we used the &lt;a href="https://www.w3.org/TR/css-page-3/#page-based-counters" rel="noopener noreferrer"&gt;‘page’ and ‘pages’ counters&lt;/a&gt;, which are predefined in any of the PDF generation tools. To create the table of contents, we then used the &lt;a href="https://www.w3.org/TR/css-gcpm-3/#target-counter" rel="noopener noreferrer"&gt;‘target-counter’ function&lt;/a&gt; on the headlines to figure out on which pages they are.&lt;/p&gt;

&lt;p&gt;But let’s look at counters once again as you do not need to stick to the predefined ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Counter
&lt;/h2&gt;

&lt;p&gt;To use a counter you first need to initialize it by using the &lt;a href="https://www.w3schools.com/css/css_counters.asp" rel="noopener noreferrer"&gt;‘counter-reset’ property&lt;/a&gt;. By default, the reset sets your counter to 0. But you can also set the counter to a specific number by passing it as a second parameter.&lt;/p&gt;

&lt;p&gt;To display a counter in your document, you can use the ‘counter’ function inside the ‘content’ property.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;div.content { 
  counter-reset:item; 
}

div.content &amp;gt; div { 
  counter-increment: item;
}

div.content &amp;gt; div:before { 
  content: counter(item) " - ";  
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The HTML structure for the above CSS would be a div element with the class ‘content’, containing further div elements. The child div elements will get counted.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="content"&amp;gt;
 &amp;lt;h1&amp;gt;PrintCSS: Counter&amp;lt;/h1&amp;gt;
 &amp;lt;div&amp;gt;first content div&amp;lt;/div&amp;gt;
 &amp;lt;div&amp;gt;second content div&amp;lt;/div&amp;gt;
 &amp;lt;div&amp;gt;third content div&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fomj777nb9w6skg07e3m7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fomj777nb9w6skg07e3m7.png" alt="The result of the above HTML and CSS code" width="291" height="286"&gt;&lt;/a&gt;&lt;em&gt;The result of the above HTML and CSS code&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As I mentioned before, we can directly set the counter to a different number. For this, we need to adjust our CSS code.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;div.content { 
  counter-reset:item 4; 
}

div.content &amp;gt; div { 
  counter-increment: item;
}

div.content &amp;gt; div:before { 
  content: counter(item) " - ";  
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fcw34cyy5ulkm9ot53d6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fcw34cyy5ulkm9ot53d6g.png" alt="Counter Reset with the value 4." width="277" height="268"&gt;&lt;/a&gt;&lt;em&gt;Counter Reset with the value 4.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now take a guess what number our ‘first content div’ would start with? The correct answer is 5. Why 5, you may ask, because we increment the counter by one for every div inside the content div.&lt;/p&gt;

&lt;p&gt;As we saw until now, the counter increment is one by default, but also here; we can pass any other number. Let’s, for example, increment in steps of five and use the default reset of 0.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;div.content { 
  counter-reset:item; 
}

div.content &amp;gt; div { 
  counter-increment: item 5;
}

div.content &amp;gt; div:before { 
  content: counter(item) " - ";
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This will return us 5, 10, and 15 as the numbers in front of our content divs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F41u565hisip3j9xa1nlw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F41u565hisip3j9xa1nlw.png" alt="Counter Increment by steps of 5." width="287" height="291"&gt;&lt;/a&gt;&lt;em&gt;Counter Increment by steps of 5.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can also pass a negative number to the counter increment to lower the current value. So let’s say we do the reset to 4 again and then increment by -1.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;div.content { 
  counter-reset:item 4; 
}

div.content &amp;gt; div { 
  counter-increment: item -1;
}

div.content &amp;gt; div:before { 
  content: counter(item) " - ";  
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This will give our content div’s the numbers 3, 2, and 1.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fp8826sm04zvqw7q82hbt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fp8826sm04zvqw7q82hbt.png" alt="Counter Increment with a negative number" width="276" height="276"&gt;&lt;/a&gt;&lt;em&gt;Counter Increment with a negative number&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Also, the counter reset allows you to use negative numbers, and we can count up with the counter increment.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;div.content { 
  counter-reset:item -1; 
}

div.content &amp;gt; div { 
  counter-increment: item;
}

div.content &amp;gt; div:before { 
  content: counter(item) " - ";  
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fblj6558715nxp08rv4wf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fblj6558715nxp08rv4wf.png" alt="Negative counter reset and counting up with the default increment" width="293" height="277"&gt;&lt;/a&gt;&lt;em&gt;Negative counter reset and counting up with the default increment&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Nested Counter
&lt;/h2&gt;

&lt;p&gt;You can nest counters to create something like 1.2.3 or 2.1. Let us have a look at how that is done. First, we will need to create a nested HTML structure.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="content"&amp;gt;
 &amp;lt;h1&amp;gt;PrintCSS: Nested Counter&amp;lt;/h1&amp;gt;
 &amp;lt;ol&amp;gt;
   &amp;lt;li&amp;gt;item&amp;lt;/li&amp;gt;
   &amp;lt;li&amp;gt;item
  &amp;lt;ol&amp;gt;
    &amp;lt;li&amp;gt;item&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;item&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;item
   &amp;lt;ol&amp;gt;
     &amp;lt;li&amp;gt;item&amp;lt;/li&amp;gt;
     &amp;lt;li&amp;gt;item&amp;lt;/li&amp;gt;
   &amp;lt;/ol&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;item&amp;lt;/li&amp;gt;
  &amp;lt;/ol&amp;gt;
   &amp;lt;/li&amp;gt;
   &amp;lt;li&amp;gt;item&amp;lt;/li&amp;gt;
   &amp;lt;li&amp;gt;item&amp;lt;/li&amp;gt;
 &amp;lt;/ol&amp;gt;

 &amp;lt;ol&amp;gt;
   &amp;lt;li&amp;gt;item&amp;lt;/li&amp;gt;
   &amp;lt;li&amp;gt;item&amp;lt;/li&amp;gt;
 &amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To display our counter value now, we need to use the function ‘counters’ instead of ‘counter’. This function requires a second parameter, which is the separator of the nested counters. The rest of the CSS is quite similar, only the selectors change.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ol {
  counter-reset: listnumber;
  list-style-type: none;
}

li{
 counter-increment: listnumber;
}

li::before {
  content: counters(listnumber, ".") " ";
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fep921fy642g5z4oeux0t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fep921fy642g5z4oeux0t.png" alt="A list with nested counters" width="463" height="612"&gt;&lt;/a&gt;&lt;em&gt;A list with nested counters&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Everything else works exactly like before, for example incrementing by 5 instead of 1.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ol {
  counter-reset: listnumber;
  list-style-type: none;
}

li{
 counter-increment: listnumber 5;
}

li::before {
  content: counters(listnumber, ".") " ";
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Foynp852r2ldlpvfhd6c7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Foynp852r2ldlpvfhd6c7.png" alt="Nested counter with increments of 5" width="453" height="590"&gt;&lt;/a&gt;&lt;em&gt;Nested counter with increments of 5&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross References
&lt;/h2&gt;

&lt;p&gt;Cross References are usually used to point to a chapter or page number. The most common example is the table of contents.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fqgkya54u2zepjm8n92jg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fqgkya54u2zepjm8n92jg.png" alt="The first counter example from this article" width="279" height="277"&gt;&lt;/a&gt;&lt;em&gt;The first counter example from this article&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Looking back to our counter example with the content div’s. Let’s say we wanna reference to the second content div. For this to work, we first need to give the HTML element an ID.&lt;/p&gt;

&lt;p&gt;Then we create a text anchor to this ID and make the ‘marker’ IDs content bold and red.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a href="#marker"&amp;gt;The magic number is&amp;lt;/a&amp;gt;
&amp;lt;div&amp;gt;first content div&amp;lt;/div&amp;gt;
 &amp;lt;div id="marker"&amp;gt;second content div&amp;lt;/div&amp;gt;
 &amp;lt;div&amp;gt;third content div&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fjs7frwe72zdpzjvom25k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fjs7frwe72zdpzjvom25k.png" alt="The new HTML and CSS" width="294" height="315"&gt;&lt;/a&gt;&lt;em&gt;The new HTML and CSS&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To get the ‘magic’ number, or better said to cross-reference to this counter value, we need to use the target-counter function.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.content a::after {
  content: ": " target-counter(attr(href, url), item)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;First, we say that we want the ‘href’ attribute, and then we say which counter we want to get in our case ‘item’. And in this way, we will get our ‘magic’ number 2!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F1oydq0ammonpk6h15g09.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F1oydq0ammonpk6h15g09.png" alt="Using target-counter to get the counter value on the second div" width="310" height="320"&gt;&lt;/a&gt;&lt;em&gt;Using target-counter to get the counter value on the second div&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Last let’s try the same on our nested counter sample from above. Of course, we need to change the counter name in the target-counter function to ‘listnumber’. We will put the marker ID on the item with the counter value 2.3.2.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fpr7eaywlbztb7tum8xpy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fpr7eaywlbztb7tum8xpy.png" alt="Target-counter on the nested counters, we should get 2.3.2 but we get 2" width="452" height="653"&gt;&lt;/a&gt;&lt;em&gt;Target-counter on the nested counters, we should get 2.3.2 but we get 2&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As you see, we still get 2 as our ‘magic’ number and not 2.3.2 cause we use target-counter. This only gives us the last counter, so as we want 2.3.2, the last number in this is ‘2’. If we would have put the marker on the 2.3.1 element, we would have gotten 1 in return.&lt;/p&gt;

&lt;p&gt;Here, we need to use the ‘target-counters’ function and add another parameter at the end, the separator for the single counter values.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.content a::after {
  content: ": " target-counters(attr(href, url), listnumber, '.')
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fb7732md5izxm28sgq0z8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fb7732md5izxm28sgq0z8.png" alt="The correct result with target-counters" width="447" height="658"&gt;&lt;/a&gt;&lt;em&gt;The correct result with target-counters&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;All the samples in this article are rendered with &lt;a href="https://www.pdfreactor.com/" rel="noopener noreferrer"&gt;PDFreactor&lt;/a&gt; on the website &lt;a href="https://printcss.live/" rel="noopener noreferrer"&gt;printcss.live&lt;/a&gt;. The samples are working with &lt;a href="https://www.princexml.com/" rel="noopener noreferrer"&gt;Prince&lt;/a&gt; and &lt;a href="https://weasyprint.org/" rel="noopener noreferrer"&gt;Weasyprint&lt;/a&gt; too!&lt;/p&gt;

</description>
      <category>html</category>
      <category>css</category>
      <category>printcss</category>
      <category>counters</category>
    </item>
    <item>
      <title>PrintCSS: Page Selectors and Page Breaks</title>
      <dc:creator>Andreas</dc:creator>
      <pubDate>Sat, 27 Feb 2021 23:06:12 +0000</pubDate>
      <link>https://dev.to/azettl/printcss-page-selectors-and-page-breaks-j07</link>
      <guid>https://dev.to/azettl/printcss-page-selectors-and-page-breaks-j07</guid>
      <description>&lt;p&gt;In this article, you will learn everything about page selectors and breaks. Both are very important when you plan to style a multi-page publication, like booklets.&lt;/p&gt;

&lt;p&gt;You will learn how to start each chapter on a separate page and style the left and right pages differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Page Selectors and Breaks
&lt;/h2&gt;

&lt;p&gt;Using the &lt;a href="https://www.w3.org/TR/css-page-3/#at-page-rule" rel="noopener noreferrer"&gt;‘@page’ rule&lt;/a&gt;, you can give a general style to all pages. But if you want to be more specific, page selectors like &lt;a href="https://www.w3.org/TR/css-page-3/#spread-pseudos" rel="noopener noreferrer"&gt;left, right&lt;/a&gt;, and &lt;a href="https://www.w3.org/TR/css-page-3/#first-pseudo" rel="noopener noreferrer"&gt;first&lt;/a&gt; need to be used.&lt;/p&gt;

&lt;p&gt;To see how these selectors work, let us start with a basic multi-page document. You can copy below HTML and CSS code into the &lt;a href="https://printcss.live/" rel="noopener noreferrer"&gt;printcss.live editor&lt;/a&gt; to follow this article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTML:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="headerLeft"&amp;gt;
  Page Selectors
&amp;lt;/div&amp;gt;
&amp;lt;div class="headerRight"&amp;gt;
  A sample document
&amp;lt;/div&amp;gt;

&amp;lt;div class="footerLeft"&amp;gt;
  &amp;lt;img src="[https://printcss.live/img/logo.png](https://printcss.live/img/logo.png)" /&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class="footerRight"&amp;gt;
  &amp;lt;a href="[info@azettl.net](mailto:info@azettl.net)"&amp;gt;[info@azettl.net](mailto:info@azettl.net)&amp;lt;/a&amp;gt;&amp;lt;br /&amp;gt;
  &amp;lt;a href="[https://printcss.live/](https://printcss.live/)"&amp;gt;printcss.live&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class="toc"&amp;gt;
  &amp;lt;h1&amp;gt;Table of contents&amp;lt;/h1&amp;gt;
  &amp;lt;a href="#running"&amp;gt;PrintCSS: Running Headers and Footers&amp;lt;/a&amp;gt;
  &amp;lt;a href="#toc"&amp;gt;PrintCSS: Table of Contents&amp;lt;/a&amp;gt;
  &amp;lt;a href="#footnotes"&amp;gt;PrintCSS: Footnotes&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class="content"&amp;gt;
  &amp;lt;h1 id="running"&amp;gt;PrintCSS: Running Headers and Footers&amp;lt;/h1&amp;gt;
  &amp;lt;p&amp;gt;
    If you want to use HTML inside the page margin boxes, you will need to use running elements. These elements are normal HTML nodes with the CSS position property set to 'running(NAME)'.
  &amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;
    The name given to the running element is used within the page margin boxes to set their content to the running elements inner HTML.
  &amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;
    To do so, you need to set the specific page margin box's CSS content property to 'element(NAME)', where NAME is the given running element name.
  &amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;
    Elements with the position running do not appear in the document until you assign them to some CSS content property.
  &amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;
    Your running elements must come first in your HTML structure. For example, if you put the footer element before the closing body tag, the running footer will only appear on the last page.
  &amp;lt;/p&amp;gt;

&amp;lt;h1 id="toc"&amp;gt;PrintCSS: Table of Contents&amp;lt;/h1&amp;gt;

&amp;lt;p&amp;gt;A very common use case for your PrintCSS document is creating a table of contents. But how would you know the page numbers of your chapters before you render the document? The content takes as many pages as it needs and if your content is dynamic it is impossible to say how many pages a chapter would result in.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Before we start with our table of contents we will need some content to create the table for. So let's take the result from my previous article PrintCSS: Running Headers and Footers and extend the content.&amp;lt;/p&amp;gt;

  &amp;lt;h1 id="footnotes"&amp;gt;PrintCSS: Footnotes&amp;lt;/h1&amp;gt;

  &amp;lt;p&amp;gt;Footnotes play an important role when it comes to creating books or PDF documents in general. Unfortunately, not all PDF rendering tools are supporting the W3C draft yet. While the commercial tools all have a good integration of the draft, most open-source tools lack support for footnotes completely.&amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;Still, in this article, I will explain the W3C footnote draft to you, and we will see the rendering result with the different tools.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;CSS:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page  {
  size: A6;
  margin: 20mm;

@top-left{
    content: element(headerLeft);
    border-bottom:2px solid #434190;
  }

@top-center{
    border-bottom:2px solid #434190;
  }

@top-right{
    content: element(headerRight);
    border-bottom:2px solid #434190;
  }

@bottom-right {
    content: element(footerRight);
    border-top:2px solid #434190;
  }

@bottom-center{
    content: counter(page) ' / ' counter(pages);
    border-top:2px solid #434190;
    font-size:8pt;
  }

@bottom-left{
    content: element(footerLeft);
    border-top:2px solid #434190;
  }
}

.headerLeft{
  position: running(headerLeft);
  font-size:12pt;
}

.headerRight{
  position: running(headerRight);
  font-size:8pt;
  font-style: italic;
  text-align: right;
  color:#667eea;
}

.footerLeft{
  position: running(footerLeft);
}

.footerLeft img{
  width:20mm;
}

.footerRight{
  position: running(footerRight);
  text-align: right;
  font-size:8pt;
}

.toc{
  break-after:always;
  page-break-after:always;
}

.toc a{
  display:block;
}

.toc a::after { 
  content: leader(".") target-counter(attr(href), page); 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now let us add a cover page.&lt;/p&gt;

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

**&amp;lt;div class="cover"&amp;gt;
  &amp;lt;h1&amp;gt;Cover Page&amp;lt;/h1&amp;gt;
&amp;lt;/div&amp;gt;**

&amp;lt;div class="toc"&amp;gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For this, we will already make use of the &lt;a href="https://www.w3.org/TR/css-break-3/#break-between" rel="noopener noreferrer"&gt;‘break-after’&lt;/a&gt; property. This way, we ensure that only our cover text is on the first page. For compatibility, we will also add the &lt;a href="https://www.w3.org/TR/css-break-3/#page-break-properties" rel="noopener noreferrer"&gt;‘page-break-after’&lt;/a&gt; property.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.cover{
  break-after:always;
  page-break-after:always;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fudmaxipg6g304vuprfse.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fudmaxipg6g304vuprfse.png" alt="The new cover page." width="800" height="569"&gt;&lt;/a&gt;&lt;em&gt;The new cover page.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  First Page
&lt;/h3&gt;

&lt;p&gt;But still, the first page has the same header and footer as all other pages. Usually, you do not want the same or any header/footer on the first page. To remove this information, you can use the &lt;a href="https://www.w3.org/TR/css-page-3/#first-pseudo" rel="noopener noreferrer"&gt;‘:first’&lt;/a&gt; pseudo-class. Within this selector, you can give the first page different content in the &lt;a href="https://medium.com/printcss/printcss-page-margin-boxes-9b89d3428cf5" rel="noopener noreferrer"&gt;page margin boxes&lt;/a&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page:first{
  @top-left{
    content:"";
    border-bottom:none;
  }

  @top-center{
    border-bottom:none;
  }

  @top-right{
    content:"";
    border-bottom:none;
  }

  @bottom-right {
    content:"";
    border-top:none;
  }

  @bottom-center{
    content:"";
    border-top:none;
  }

  @bottom-left{
    content:"";
    border-top:none;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F0l6c10m95cfiss1ufeqv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F0l6c10m95cfiss1ufeqv.png" alt="First page with empty page margin boxes." width="800" height="568"&gt;&lt;/a&gt;&lt;em&gt;First page with empty page margin boxes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Looking at the above screenshot, you see the difference between the first and second page. You see that the third and fourth page still have the same header/footer in the below screenshot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fl2mw2282nd0ghhfct7oo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fl2mw2282nd0ghhfct7oo.png" alt="The third and fourth page." width="800" height="565"&gt;&lt;/a&gt;&lt;em&gt;The third and fourth page.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Left and Right Pages
&lt;/h3&gt;

&lt;p&gt;Another common use case is printing the page numbers on the outside bottom corner of your page. Or showing the chapter’s title only on the left page. So to differ between the pages you can use the &lt;a href="https://www.w3.org/TR/css-page-3/#spread-pseudos" rel="noopener noreferrer"&gt;‘:left’&lt;/a&gt; and &lt;a href="https://www.w3.org/TR/css-page-3/#spread-pseudos" rel="noopener noreferrer"&gt;‘:right’&lt;/a&gt; pseudo-classes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F9000%2F1%2Atkm2ef4CxgpLvkLCnBO1Og.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F9000%2F1%2Atkm2ef4CxgpLvkLCnBO1Og.jpeg" alt="Picture by Tamara Gak @tamara_photography, [https://unsplash.com/photos/IBXcdiq-o0A](https://unsplash.com/photos/IBXcdiq-o0A)" width="800" height="533"&gt;&lt;/a&gt;&lt;em&gt;Picture by Tamara Gak @tamara_photography, &lt;a href="https://unsplash.com/photos/IBXcdiq-o0A" rel="noopener noreferrer"&gt;https://unsplash.com/photos/IBXcdiq-o0A&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We need to define different content in the &lt;a href="https://medium.com/printcss/printcss-page-margin-boxes-9b89d3428cf5" rel="noopener noreferrer"&gt;page margin boxes&lt;/a&gt; for the left and right pages to achieve this.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
@page:left{
  @bottom-left {
    content:"I am a left page";
    border-top:2px solid #434190;
    font-size:8pt;
  }
}

@page:right{
  @bottom-right {
    content: "I am a right page";
    border-top:2px solid #434190;
    font-size:8pt;
  }
}
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can see that we overwrite the bottom page margin boxes for the right and left pages in the CSS code above.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F73gq3lzmnlyuus6da3ju.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F73gq3lzmnlyuus6da3ju.png" alt="Left and right pages" width="800" height="568"&gt;&lt;/a&gt;&lt;em&gt;Left and right pages&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now let’s put the page numbers to the outside and hide the rest.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
@page:left{
  @bottom-right {
    content:"";
    border-top:2px solid #434190;
  }

  @bottom-center{
    content:"";
    border-top:2px solid #434190;
  }

  @bottom-left{
    content: counter(page) ' / ' counter(pages);
    border-top:2px solid #434190;
    font-size:8pt;
  }
}

@page:right{
  @bottom-right {
    content: counter(page) ' / ' counter(pages);
    border-top:2px solid #434190;
    font-size:8pt;
  }

  @bottom-center{
    content:"";
    border-top:2px solid #434190;
  }

  @bottom-left{
    content:"";
    border-top:2px solid #434190;
  }
}
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fdfz7hw5d20e281anqjkr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdfz7hw5d20e281anqjkr.png" alt="Adjusted Page Numbers on Left and Right Pages" width="800" height="567"&gt;&lt;/a&gt;&lt;em&gt;Adjusted Page Numbers on Left and Right Pages&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Blank Pages
&lt;/h3&gt;

&lt;p&gt;Now what to do about empty pages? Most of the time, you do not want a page number or any header on pages without content. For this, there is a pseudo-class called &lt;a href="https://www.w3.org/TR/css-page-3/#blank-pseudo" rel="noopener noreferrer"&gt;‘blank’&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let us first prepare our document again, so we let each chapter start on a right page. With this, the left page might be empty in some cases, as the previous chapter’s content could end on a right page.&lt;/p&gt;

&lt;p&gt;Look at the CSS below. Instead of passing an ‘always’ to the &lt;a href="https://www.w3.org/TR/css-break-3/#break-between" rel="noopener noreferrer"&gt;‘break-before’&lt;/a&gt;, we pass a ‘right’. This way, the content of a chapter will always start on an right page.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.content h1{
  break-before:right;
  page-break-before:right;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The screenshot below shows how the chapter’s content starts on the right page, and the left page is empty.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F07fjirr6osxq3ya1ks5o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F07fjirr6osxq3ya1ks5o.png" alt="Left Page without Content" width="800" height="568"&gt;&lt;/a&gt;&lt;em&gt;Left Page without Content&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Not empty, though, as we still have the header and footer. Now, let’s use the &lt;a href="https://www.w3.org/TR/css-page-3/#blank-pseudo" rel="noopener noreferrer"&gt;‘blank’&lt;/a&gt; pseudo-class and remove those.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page:blank  {

  @top-left{
    content:"";
    border-bottom:none;
  }

  @top-center{
    content:"";
    border-bottom:none;
  }

  @top-right{
    content:"";
    border-bottom:none;
  }

  @bottom-right {
    content:"";
    border-top:none;
  }

  @bottom-center{
    content:"";
    border-top:none;
  }

  @bottom-left{
    content:"";
    border-top:none;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F8dimuqkd04bglf4rj3kh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F8dimuqkd04bglf4rj3kh.png" alt="A empty left page." width="800" height="571"&gt;&lt;/a&gt;&lt;em&gt;A empty left page.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;All examples in this article were rendered with &lt;a href="https://www.pdfreactor.com/" rel="noopener noreferrer"&gt;PDFreactor&lt;/a&gt;. You can try the other PDF rendering tools too. These samples will work with most of them!&lt;/p&gt;

&lt;p&gt;Another topic which I will cover in a different article is &lt;a href="https://www.w3.org/TR/css-page-3/#using-named-pages" rel="noopener noreferrer"&gt;‘named pages’&lt;/a&gt;. So if you liked the content of this article, follow my blog for more!&lt;/p&gt;

</description>
      <category>html</category>
      <category>css</category>
      <category>pages</category>
      <category>printcss</category>
    </item>
    <item>
      <title>PrintCSS: A basic CV example</title>
      <dc:creator>Andreas</dc:creator>
      <pubDate>Sat, 27 Feb 2021 23:04:11 +0000</pubDate>
      <link>https://dev.to/azettl/printcss-a-basic-cv-example-2k2</link>
      <guid>https://dev.to/azettl/printcss-a-basic-cv-example-2k2</guid>
      <description>&lt;p&gt;In this article you will learn how to create your own CV PDF with HTML, CSS. To render the actual PDF we will use &lt;a href="https://www.pagedjs.org/" rel="noopener noreferrer"&gt;PagedJS&lt;/a&gt; an open-source tool to create PDF documents from HTML.&lt;/p&gt;

&lt;p&gt;Before we start with the CV, let us think about the structure we want. So in this example we will create a multi-page CV and on each of the pages we will have our main details in a header section.&lt;/p&gt;

&lt;p&gt;After the header there will be a short section for a introduction text. Then following a section for the employment history, the education and further skills.&lt;/p&gt;

&lt;p&gt;For all sections there will be sub-sections for the different jobs or achievements. On the left side we will put the dates and right the content so what we actually did in that time period.&lt;/p&gt;

&lt;h2&gt;
  
  
  The header section
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F8bli63snyam9966lcuvr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F8bli63snyam9966lcuvr.png" alt="Header Section Sample" width="800" height="200"&gt;&lt;/a&gt;&lt;em&gt;Header Section Sample&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For the header let us first define the HTML structure. Our heading will be the first and last name in an H1 element. Next we want to put some links to important websites, like our LinkedIn or GitHub Profile. That these links look nice you can include &lt;a href="https://fontawesome.com/" rel="noopener noreferrer"&gt;FontAwesome&lt;/a&gt; and add a small icon in front of every link. Last we should put more information about ourselves, like the birthdate, phone number and address.&lt;/p&gt;

&lt;p&gt;CSS:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@import url('https://fonts.googleapis.com/css2?family=Asul:wght@700&amp;amp;family=Open+Sans&amp;amp;display=swap');
@import url('https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css');

@page{
  size:A4;
  margin:5cm 2cm 2cm 2cm;

  @top-center{
    content:element(MyHeader);
  }

  @bottom-center{
    content: "- " counter(page) " -";
  }
}

header{
    position:running(MyHeader);
    text-align:center;
}

header p i:first-of-type{
    margin-left:0;
}

header i{
    margin-left:5mm;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;HTML: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;header&amp;gt;
    &amp;lt;h1&amp;gt;Firstname Lastname&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;
        &amp;lt;i class="fa fa-envelope" aria-hidden="true"&amp;gt;&amp;lt;/i&amp;gt; &amp;lt;a href="mailto:info@azettl.net"&amp;gt;info@azettl.net&amp;lt;/a&amp;gt; 
        &amp;lt;i class="fa fa-home" aria-hidden="true"&amp;gt;&amp;lt;/i&amp;gt; &amp;lt;a href="https://printcss.live"&amp;gt;printcss.live&amp;lt;/a&amp;gt;
        &amp;lt;i class="fa fa-github" aria-hidden="true"&amp;gt;&amp;lt;/i&amp;gt; &amp;lt;a href="https://github.com/"&amp;gt;github.com/USER&amp;lt;/a&amp;gt;
        &amp;lt;i class="fa fa-linkedin" aria-hidden="true"&amp;gt;&amp;lt;/i&amp;gt; &amp;lt;a href="https://linkedin.com/"&amp;gt;linkedin.com/in/USER&amp;lt;/a&amp;gt;
    &amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;
        &amp;lt;i class="fa fa-birthday-cake" aria-hidden="true"&amp;gt;&amp;lt;/i&amp;gt; 01. January 1970 
        &amp;lt;i class="fa fa-phone" aria-hidden="true"&amp;gt;&amp;lt;/i&amp;gt; +49 123 456 789 
        &amp;lt;i class="fa fa-map-marker" aria-hidden="true"&amp;gt;&amp;lt;/i&amp;gt; Streetname 1, 12345 City
    &amp;lt;/p&amp;gt;
&amp;lt;/header&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  The profile section
&lt;/h2&gt;

&lt;p&gt;The profile section or introduction text will be our first section. Here we already define the split of left and right content in the sub-sections. We will use the same code in all following sections. Let’s put the sections title in an H2 element and for the left/right sub-sections we will use a flex layout. To do so you need to set the display property of the element to ‘flex’. The left element should have a minimum width and the right element will take the rest of the space.&lt;/p&gt;

&lt;p&gt;As the right element has paragraphs or further headlines (H3 elements) inside you should set their margin top to 0. This way they start at the same level as the left sides content.&lt;/p&gt;

&lt;p&gt;CSS:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  section:first-of-type{
    position:absolute;
      bottom:0;
  }

  section:nth-of-type(2){
      break-before:always;
  }

  section{
    break-inside:avoid;
  }

  h1, h2, h3{
    font-family: 'Asul', sans-serif;
  }

  h2{
    background-color:black;
      color:white;
      display:inline-block;
      padding:1mm;
      text-transform:uppercase;
  }

  body{
      font-family: 'Open Sans', sans-serif;
  }

  .flex{
    display:flex;
  }

  .left{
    min-width:4cm;
  }

  .right p:first-of-type,
  .right h3:first-of-type{
    margin-top:0;
  }

  .right p:first-of-type{
    margin-bottom:0;
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;HTML: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;section&amp;gt;
    &amp;lt;h2&amp;gt;Profile&amp;lt;/h2&amp;gt;
    &amp;lt;div class="flex"&amp;gt;
        &amp;lt;div class="left"&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;div class="right"&amp;gt;
            &amp;lt;p&amp;gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. A erat nam at lectus urna duis. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar. Risus ultricies tristique nulla aliquet enim tortor at auctor urna. Justo donec enim diam vulputate ut pharetra sit amet aliquam. Eros donec ac odio tempor orci dapibus ultrices in. Libero enim sed faucibus turpis in eu mi bibendum.&amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;Imperdiet nulla malesuada pellentesque elit eget gravida cum sociis. Pellentesque habitant morbi tristique senectus et netus et. Gravida in fermentum et sollicitudin ac. Ac odio tempor orci dapibus ultrices.&amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;Nunc mattis enim ut tellus elementum. Diam quam nulla porttitor massa id neque aliquam. Auctor urna nunc id cursus metus aliquam. Nunc congue nisi vitae suscipit. Ut eu sem integer vitae justo eget. &amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/section&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  The other sections
&lt;/h2&gt;

&lt;p&gt;All other sections in your CV document will have the same HTML structure and CSS styling. Have a look at the complete sample below to see how these sections are added.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta charset="UTF-8"&amp;gt;
        &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&amp;gt;
        &amp;lt;title&amp;gt;Firstname Lastname - CV&amp;lt;/title&amp;gt;
        &amp;lt;style&amp;gt;
          @import url('https://fonts.googleapis.com/css2?family=Asul:wght@700&amp;amp;family=Open+Sans&amp;amp;display=swap');
          @import url('https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css');

          @page{
            size:A4;
            margin:5cm 2cm 2cm 2cm;

            @top-center{
              content:element(MyHeader);
            }

            @bottom-center{
              content: "- " counter(page) " -";
            }
          }

          header{
            position:running(MyHeader);
              text-align:center;
          }

          header p i:first-of-type{
            margin-left:0;
          }

          header i{
            margin-left:5mm;
          }

          section:first-of-type{
            position:absolute;
              bottom:0;
          }

          section:nth-of-type(2){
              break-before:always;
          }

          section{
            break-inside:avoid;
          }

          h1, h2, h3{
            font-family: 'Asul', sans-serif;
          }

          h2{
            background-color:black;
              color:white;
              display:inline-block;
              padding:1mm;
              text-transform:uppercase;
          }

          body{
              font-family: 'Open Sans', sans-serif;
          }

          .flex{
            display:flex;
          }

          .left{
            min-width:4cm;
          }

          .right p:first-of-type,
          .right h3:first-of-type{
            margin-top:0;
          }

          .right p:first-of-type{
            margin-bottom:0;
          }
        &amp;lt;/style&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;header&amp;gt;
            &amp;lt;h1&amp;gt;Firstname Lastname&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;
                &amp;lt;i class="fa fa-envelope" aria-hidden="true"&amp;gt;&amp;lt;/i&amp;gt; &amp;lt;a href="mailto:info@azettl.net"&amp;gt;info@azettl.net&amp;lt;/a&amp;gt; 
                &amp;lt;i class="fa fa-home" aria-hidden="true"&amp;gt;&amp;lt;/i&amp;gt; &amp;lt;a href="https://printcss.live"&amp;gt;printcss.live&amp;lt;/a&amp;gt;
                &amp;lt;i class="fa fa-github" aria-hidden="true"&amp;gt;&amp;lt;/i&amp;gt; &amp;lt;a href="https://github.com/"&amp;gt;github.com/USER&amp;lt;/a&amp;gt;
                &amp;lt;i class="fa fa-linkedin" aria-hidden="true"&amp;gt;&amp;lt;/i&amp;gt; &amp;lt;a href="https://linkedin.com/"&amp;gt;linkedin.com/in/USER&amp;lt;/a&amp;gt;
            &amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;
                &amp;lt;i class="fa fa-birthday-cake" aria-hidden="true"&amp;gt;&amp;lt;/i&amp;gt; 01. January 1970 
                &amp;lt;i class="fa fa-phone" aria-hidden="true"&amp;gt;&amp;lt;/i&amp;gt; +49 123 456 789 
                &amp;lt;i class="fa fa-map-marker" aria-hidden="true"&amp;gt;&amp;lt;/i&amp;gt; Streetname 1, 12345 City
            &amp;lt;/p&amp;gt;
        &amp;lt;/header&amp;gt;

        &amp;lt;section&amp;gt;
            &amp;lt;h2&amp;gt;Profile&amp;lt;/h2&amp;gt;
            &amp;lt;div class="flex"&amp;gt;
                &amp;lt;div class="left"&amp;gt;&amp;lt;/div&amp;gt;
                &amp;lt;div class="right"&amp;gt;
                    &amp;lt;p&amp;gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. A erat nam at lectus urna duis. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar. Risus ultricies tristique nulla aliquet enim tortor at auctor urna. Justo donec enim diam vulputate ut pharetra sit amet aliquam. Eros donec ac odio tempor orci dapibus ultrices in. Libero enim sed faucibus turpis in eu mi bibendum.&amp;lt;/p&amp;gt;
                    &amp;lt;p&amp;gt;Imperdiet nulla malesuada pellentesque elit eget gravida cum sociis. Pellentesque habitant morbi tristique senectus et netus et. Gravida in fermentum et sollicitudin ac. Ac odio tempor orci dapibus ultrices.&amp;lt;/p&amp;gt;
                    &amp;lt;p&amp;gt;Nunc mattis enim ut tellus elementum. Diam quam nulla porttitor massa id neque aliquam. Auctor urna nunc id cursus metus aliquam. Nunc congue nisi vitae suscipit. Ut eu sem integer vitae justo eget. &amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/section&amp;gt;

        &amp;lt;section&amp;gt;
            &amp;lt;h2&amp;gt;Employment History&amp;lt;/h2&amp;gt;
            &amp;lt;div class="flex"&amp;gt;
                &amp;lt;div class="left"&amp;gt;2020 January &amp;amp;ndash; today&amp;lt;/div&amp;gt;
                &amp;lt;div class="right"&amp;gt;
                &amp;lt;p&amp;gt;Excepteur&amp;lt;/p&amp;gt;
                &amp;lt;h3&amp;gt;Duis aute irure dolor in reprehenderit in voluptate&amp;lt;/h3&amp;gt;
                &amp;lt;ul&amp;gt;
                    &amp;lt;li&amp;gt;Lorem ipsum dolor sit amet&amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;Lorem ipsum dolor sit amet&amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;Lorem ipsum dolor sit amet&amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;Lorem ipsum dolor sit amet&amp;lt;/li&amp;gt;
                &amp;lt;/ul&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="flex"&amp;gt;
                &amp;lt;div class="left"&amp;gt;2007 &amp;amp;ndash; today&amp;lt;/div&amp;gt;
                &amp;lt;div class="right"&amp;gt;
                &amp;lt;p&amp;gt;Lorem ipsum dolor&amp;lt;/p&amp;gt;
                &amp;lt;h3&amp;gt;Lorem ipsum&amp;lt;/h3&amp;gt;
                &amp;lt;p&amp;gt;
                    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
                &amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/section&amp;gt;

        &amp;lt;section&amp;gt;
        &amp;lt;h2&amp;gt;Education&amp;lt;/h2&amp;gt;
            &amp;lt;div class="flex"&amp;gt;
                &amp;lt;div class="left"&amp;gt;2004 &amp;amp;ndash; 2007&amp;lt;/div&amp;gt;
                &amp;lt;div class="right"&amp;gt;
                    &amp;lt;h3&amp;gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit&amp;lt;/h3&amp;gt;
                    &amp;lt;p&amp;gt;sed do eiusmod tempor incididunt ut labore et dolore magna aliqua&amp;lt;/p&amp;gt;
                    &amp;lt;ul&amp;gt;
                        &amp;lt;li&amp;gt;Lorem ipsum dolor sit amet&amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;Lorem ipsum dolor sit amet&amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;Lorem ipsum dolor sit amet&amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;Lorem ipsum dolor sit amet&amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;Lorem ipsum dolor sit amet&amp;lt;/li&amp;gt;
                    &amp;lt;/ul&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/section&amp;gt;

        &amp;lt;section&amp;gt;
            &amp;lt;h2&amp;gt;Skills&amp;lt;/h2&amp;gt;
            &amp;lt;div class="flex"&amp;gt;
                &amp;lt;div class="left"&amp;gt;Languages&amp;lt;/div&amp;gt;
                &amp;lt;div class="right"&amp;gt;German: mother tongue,English: fluent&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="flex"&amp;gt;
                &amp;lt;div class="left"&amp;gt;Certificates&amp;lt;/div&amp;gt;
                &amp;lt;div class="right"&amp;gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/section&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can see the complete sample including the &lt;a href="https://github.com/azettl/printcss.examples/blob/main/HTML%20Examples/CVs/Basic/result.pdf" rel="noopener noreferrer"&gt;resulting PDF document&lt;/a&gt; on &lt;a href="https://github.com/azettl/printcss.examples" rel="noopener noreferrer"&gt;my GitHub repository&lt;/a&gt; for print CSS samples.&lt;/p&gt;

&lt;p&gt;If you want to try recreating this CV or create your own one you can use &lt;a href="https://printcss.live/" rel="noopener noreferrer"&gt;printcss.live&lt;/a&gt; to render your PDF. This will save you the time of installing one of the rendering tools on your own dev environment.&lt;/p&gt;

&lt;p&gt;If you want a more detailed explanation of the above sample check the &lt;a href="https://youtu.be/EHRw_cOSs00" rel="noopener noreferrer"&gt;Video about the same on YouTube&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>html</category>
      <category>css</category>
      <category>printcss</category>
      <category>cv</category>
    </item>
  </channel>
</rss>
