<?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: Shy Ruparel</title>
    <description>The latest articles on DEV Community by Shy Ruparel (@shy).</description>
    <link>https://dev.to/shy</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%2F14347%2F5d245040-80e5-46dd-b210-ff3640bb9cbf.jpg</url>
      <title>DEV Community: Shy Ruparel</title>
      <link>https://dev.to/shy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shy"/>
    <language>en</language>
    <item>
      <title>How we turned the Replay keynote surprise into an open-source embedded playground</title>
      <dc:creator>Shy Ruparel</dc:creator>
      <pubDate>Tue, 02 Jun 2026 15:36:42 +0000</pubDate>
      <link>https://dev.to/temporalio/how-we-turned-the-replay-keynote-surprise-into-an-open-source-embedded-playground-49hm</link>
      <guid>https://dev.to/temporalio/how-we-turned-the-replay-keynote-surprise-into-an-open-source-embedded-playground-49hm</guid>
      <description>&lt;p&gt;In the &lt;a href="https://temporal.io/blog/badges-for-replay-and-i-havent-slept-since-december" rel="noopener noreferrer"&gt;first post&lt;/a&gt;, I told the story of how the Replay badge went from "wouldn't it be cool if..." to thousands of circuit boards showing up at a developer conference.&lt;/p&gt;

&lt;p&gt;This one is about what was hiding inside that surprise.&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%2Fwe7qwiqfmcoh3lq1hh4a.jpg" 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%2Fwe7qwiqfmcoh3lq1hh4a.jpg" alt="SamarBadgeKeynote" width="799" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Samar Abbas, our CEO and co-founder announcing the Replay Badges&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;During the final keynote at Replay, our CEO and co-founder, Samar Abbas, came back on stage at the end of the session for a very "one more thing" kind of moment. He wanted to give Replay attendees one more place to deploy Temporal, then revealed that we had secretly manufactured 2,000 hardware badges for the people in attendance.&lt;/p&gt;

&lt;p&gt;As attendees left the keynote hall, we handed them something that looked, at first glance, like a very over-engineered conference badge. Which, to be clear, it absolutely was. But it was also a tiny hackable computer: an ESP32-S3, an OLED screen, an LED matrix, buttons, joystick, motion sensor, haptics, IR, MicroPython, games, firmware updates, and enough sharp edges to remind me that embedded systems do not care about my feelings.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Now that the source is public on &lt;a href="https://github.com/temporal-community/badge.temporal.io" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; with our docs live at &lt;a href="https://badge.temporal.io" rel="noopener noreferrer"&gt;badge.temporal.io&lt;/a&gt;, I want to open the thing up and show you all the strange little systems inside it.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;Because we did not just open source "some firmware."&lt;/p&gt;

&lt;p&gt;We open sourced an entire ecosystem.&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%2Fimages.ctfassets.net%2F0uuz8ydxyd9p%2F6lthvMEMpX3XPzYrKeMAnD%2F7e0059108d811417b3b4f47a441a656c%2Freplay-badges-day-2-post-keynote.jpg" 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%2Fimages.ctfassets.net%2F0uuz8ydxyd9p%2F6lthvMEMpX3XPzYrKeMAnD%2F7e0059108d811417b3b4f47a441a656c%2Freplay-badges-day-2-post-keynote.jpg" alt="replay-badges-day-2-post-keynote" width="799" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Our hard work visualized. These are the Temporal badges laid out and ready for attendees to grab after the day 2 keynote. A huge moment for me and the team!&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%2Fimages.ctfassets.net%2F0uuz8ydxyd9p%2F5ir7sI4uHwmZOqEOJVy8Hd%2Fdc2cc536220aaf09840ff2801d035458%2Fpost-keynote-crowd-day-two-badges.jpg" 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%2Fimages.ctfassets.net%2F0uuz8ydxyd9p%2F5ir7sI4uHwmZOqEOJVy8Hd%2Fdc2cc536220aaf09840ff2801d035458%2Fpost-keynote-crowd-day-two-badges.jpg" alt="post-keynote-crowd-day-two-badges" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Crowds swarming the tables to pick up their badges.&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%2Fimages.ctfassets.net%2F0uuz8ydxyd9p%2F1oZjbUVKT7Dx9wYg8tDb0b%2F08050c767e6d54be131a74834ebb54d2%2FReplay-badges-with-attendees-on-elevator.jpg" 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%2Fimages.ctfassets.net%2F0uuz8ydxyd9p%2F1oZjbUVKT7Dx9wYg8tDb0b%2F08050c767e6d54be131a74834ebb54d2%2FReplay-badges-with-attendees-on-elevator.jpg" alt="Replay-badges-with-attendees-on-elevator" width="799" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Attendees checking out their badges&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What we actually open sourced
&lt;/h2&gt;

&lt;p&gt;When I say the badge is open source, I do not just mean "there is a folder of C++ somewhere." The public repo includes the firmware, hardware design package, fabrication files, docs site, release asset documentation, flashing tools, MicroPython examples, community app infrastructure, and enough context for someone else to pick up the project without needing to inherit every weird Slack thread, manufacturing panic, and half-obsolete architecture decision that got us here.&lt;/p&gt;

&lt;p&gt;The public repo looks roughly 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;firmware/        C++ runtime, MicroPython bridge, apps, Doom, OTA
hardware/        KiCad source, fab outputs, artwork, mechanical files
ignition/        Temporal-powered flashing workflows
docs/            public badge docs site
community_apps/  installable apps from contributors
data/            schedule, speaker, and floor bundles
release-assets/  firmware and factory image release notes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal was for the badge to stop being only a thing we handed out at Replay and become something people could inspect, modify, repair, reflash, extend, and, if they are brave enough, theoretically manufacture themselves.&lt;/p&gt;

&lt;p&gt;The hardware package includes KiCad projects, fabrication outputs, BOM and CPL files, mechanical references, and artwork. In theory, you can take the files we published and manufacture your own badge.&lt;/p&gt;

&lt;p&gt;In practice, this is where you discover that "open hardware" still means making friends with fab notes, component substitutions, assembly constraints, and files named things like &lt;code&gt;GBR_260331-R3.zip&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Which brings us to the first lesson.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardware has different gravity
&lt;/h2&gt;

&lt;p&gt;The hardware is where the project becomes physical. Every feature has to fit inside real power, space, component, manufacturing, and assembly constraints.&lt;/p&gt;

&lt;p&gt;The badge runs on an ESP32-S3 module with 16 MB of flash and 8 MB of PSRAM. It has a 128x64 monochrome OLED, an 8x8 LED matrix, four face buttons, an analog joystick, an accelerometer, haptics, IR send and receive, USB-C, battery power, and a two-board assembly where the backplate is also part of the visual design.&lt;/p&gt;

&lt;p&gt;That sounds like a spec list, but in hardware every bullet point is also a negotiation. The screen wants power. The LEDs want power. The battery wants to be treated like a tiny volatile roommate. The IR receiver wants timing. The buttons want debounce. The enclosure wants tolerances. The PCB wants trace routing. The manufacturer wants files in a very specific shape. The designer wants the badge to look like it belongs at Replay and not like a dev board fell into a lanyard.&lt;/p&gt;

&lt;p&gt;Our hardware guys did not believe I was going to be useful writing code at first, which is fair. From their perspective, I was a DevRel person walking into a hardware project with a lot of opinions and an alarming amount of confidence. It took them seeing my personal hardware hacking setup: the bench, the parts, the alarming amount of half-finished projects, and the Gridfinity-filled tool chest to become fully confident in me. After that, the dynamic changed a little. I was still learning their world, but I was not a tourist in hardware.&lt;/p&gt;

&lt;p&gt;I also want to be clear about something: at the beginning, I did not understand half of the hardware file types involved.&lt;/p&gt;

&lt;p&gt;KiCad projects, Gerbers, CPLs, fab zips, board backups, assembly exports, pick-and-place files — all of it looked like someone had taken a normal software repo and fed it through a manufacturing portal.&lt;/p&gt;

&lt;p&gt;Our hardware consultants were moving fast in the tools they actually use. That matters. Hardware collaboration is not shaped like web app collaboration. A lot of hardware work naturally happens through big generated files, CAD exports, board snapshots, and zip packages that are meant for humans, factories, and design tools, not for a tidy Git history.&lt;/p&gt;

&lt;p&gt;They also contributed to the C firmware along the way, so the Git problem was not just "how do I store board files?" It was also "how do I rebase active firmware changes from people who are simultaneously thinking about traces, components, and whether a factory can actually assemble this thing?"&lt;/p&gt;

&lt;p&gt;So, somehow, my job became translating all of that into Git: rebasing branches, separating source from generated artifacts, preserving the files a manufacturer would need, and cleaning the history enough that a future human could make sense of it. At some point the hardware guys started referring to me as the Merge Czar, which is not a job title I expected to earn on a PCB project, but here we are.&lt;/p&gt;

&lt;p&gt;There is a commit in the hardware history literally titled "i got spooked by the datasheet." I respect that commit deeply. That is hardware development in one sentence. You read something, your stomach drops a little, and suddenly there is a new BOM, a new CPL, a new Gerber package, and an R number attached to the end of the filename.&lt;/p&gt;

&lt;p&gt;Software teams debate commit hygiene. Hardware teams have fab revisions.&lt;/p&gt;

&lt;p&gt;Both are valid. One of them is just much heavier on zip files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Firmware as joy infrastructure
&lt;/h2&gt;

&lt;p&gt;The firmware had two jobs that were always in tension: create joy and delight for attendees, and make the badge easy enough to hack on that the delight could keep going after Replay.&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%2Fimages.ctfassets.net%2F0uuz8ydxyd9p%2F6Bgjcw1MbNw8GW0TaXJF7w%2Fb133622603213f4ae8b075c27dbe50fb%2Fattendee-at-replay-using-badge.jpg" 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%2Fimages.ctfassets.net%2F0uuz8ydxyd9p%2F6Bgjcw1MbNw8GW0TaXJF7w%2Fb133622603213f4ae8b075c27dbe50fb%2Fattendee-at-replay-using-badge.jpg" alt="attendee-at-replay-using-badge" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A Replay attendee putting our fun firmware to good use&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At first glance, firmware sounds like the boring layer. It is not. It is the layer that decides whether the badge feels alive.&lt;/p&gt;

&lt;p&gt;The native C++ firmware handles boot, the OLED UI, inputs, LED animations, haptics, IR, storage, OTA updates, app launching, power behavior, the file system, and the basic resource ownership rules that keep everything from stepping on everything else.&lt;/p&gt;

&lt;p&gt;This is where the badge became less like a single Arduino sketch and more like a tiny operating environment.&lt;/p&gt;

&lt;p&gt;Every fun thing on the badge wants to own the same small set of resources. The screen. The buttons. The joystick. The LED matrix. The IR receiver. The filesystem. Memory. Time.&lt;/p&gt;

&lt;p&gt;If you launch a MicroPython app, who owns the OLED? If Doom starts, what happens to the scheduler? What even is a scheduler? If an LED animation is running in the background, what happens when a game wants to take over the matrix? If IR is listening, how long can Python go without polling before frames start getting dropped? If a file is being written, what happens if power gets weird?&lt;/p&gt;

&lt;p&gt;These are not abstract architecture questions. These are "why is this thing frozen in my hand?" questions.&lt;/p&gt;

&lt;p&gt;The public firmware is organized into subsystems because eventually it had to be. Hardware, UI, screens, IR, boops, MicroPython, OTA, LED runtime, identity, storage, and infrastructure all got their own homes. That structure was earned, mostly by discovering what happens when too many responsibilities live in one place and then you add a deadline, a battery, and 2,000 units.&lt;/p&gt;

&lt;p&gt;The firmware is where the badge learned how to be playful without being fragile.&lt;/p&gt;

&lt;p&gt;Mostly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The badge is not a browser tab
&lt;/h2&gt;

&lt;p&gt;Most of my recent software life has been in environments where memory is something you notice when the bill arrives, the page gets slow, or a process gets very dramatic in production.&lt;/p&gt;

&lt;p&gt;Embedded work does not let you be that vague.&lt;/p&gt;

&lt;p&gt;On paper, the badge has 16 MB of flash and 8 MB of PSRAM. It also has the ESP32-S3's 512 KB of on-chip SRAM, which is technically true in the same way that an apartment technically has floor space before you move in. By the time the runtime, stacks, radios, buffers, static data, and display code take their share, the actual headroom feels much smaller.&lt;/p&gt;

&lt;p&gt;That sounds like a lot if you are thinking "tiny conference badge." It sounds like nothing if you are thinking "computer that runs a UI, games, Python apps, a file system, OTA updates, IR, LED animations, and Doom."&lt;/p&gt;

&lt;p&gt;The useful mental model is that flash is the badge's persistent storage. It survives power loss and holds the firmware, OTA update slots, filesystem image, apps, data bundles, and Doom's resources. FatFS is a partition of that flash presented like a tiny disk, which is why MicroPython apps can feel like files you can inspect and edit even though underneath it is all still a carefully partitioned chunk of SPI flash.&lt;/p&gt;

&lt;p&gt;Internal SRAM is the tiny fast workbench where the firmware keeps the things it needs right now: task stacks, driver state, Wi-Fi and TLS buffers, timing-sensitive code paths, and anything that really cannot tolerate being slow or weirdly placed. PSRAM is the bigger folding table next to it. It gives us breathing room for larger allocations like the MicroPython heap, app metadata, editor buffers, and Doom, but it is still external RAM, not a free substitute for internal SRAM.&lt;/p&gt;

&lt;p&gt;The public firmware gives MicroPython a 2 MB heap from PSRAM. That is the friendly layer where people can write apps. But the native firmware still needs memory too: task stacks, display buffers, IR queues, JSON-ish state, app registries, file buffers, OTA metadata, Doom resources, and all the small allocations you do not think about until the badge starts behaving like it has developed opinions.&lt;/p&gt;

&lt;p&gt;On the web, if you accidentally allocate a few extra objects in a render loop, maybe Chrome sighs at you. On the badge, you feel it immediately. A garbage collection pause is not just an implementation detail when the UI is trying to stay responsive and timing-sensitive hardware is still running.&lt;/p&gt;

&lt;p&gt;So memory became a design material.&lt;/p&gt;

&lt;p&gt;We reused buffers. We avoided giant temporary objects. We cared about whether something lived in internal RAM or PSRAM. We had to think about what survived reboot, what survived a firmware flash, and what would be wiped when the filesystem was replaced. We built helpers like &lt;code&gt;GCTicker&lt;/code&gt; and &lt;code&gt;DualScreenSession&lt;/code&gt; so MicroPython games could keep running without making garbage collection everyone else's problem.&lt;/p&gt;

&lt;p&gt;This is also where I learned about MessagePack. MessagePack is a binary serialization format: it carries JSON-shaped data like arrays, maps, strings, numbers, and booleans, but encodes it as compact bytes, completely dropping the benefit of being human-readable text, while keeping all the other benefits of JSON. It exists for the moments when you still want something portable and language-friendly, but you care more about size and parsing cost than being able to open the payload in a text editor.&lt;/p&gt;

&lt;p&gt;Before this project, I had almost exclusively reached for JSON because JSON is just what we all use. It is readable, debuggable, and extremely convenient. It is not what you want on a tiny device. The badge still generates JSON versions of the schedule, speaker, and floor data so humans can inspect the output, but the firmware consumes a MessagePack bundle instead. In the public data package, those JSON files are about 35.9 KB together; the packed bundle is about 20.6 KB. Saving roughly 43 percent on one data bundle is not life-changing on a laptop or server. On the badge, it was one more place where the project taught me that every comfortable default has a cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Doom wanted everything
&lt;/h2&gt;

&lt;p&gt;The OLED framebuffer is only 1024 bytes, which is adorable right up until you remember that every byte has to be drawn, rotated, copied, or handed across a boundary at the right time. The IR receive queue is small enough that "read your frames promptly" is not a suggestion. The LED matrix is tiny, but animations still want timing.&lt;/p&gt;

&lt;p&gt;Doom is probably the best way to explain how quickly the badge stopped feeling spacious. The shareware WAD, which is Doom's asset bundle, is 4,196,020 bytes. It does not live inside the firmware binary; it lives on the flash-backed FatFS partition next to the MicroPython apps and data files. Even there, it takes up about two-thirds of the badge's 6 MB filesystem partition and almost exactly one quarter of the entire 16 MB flash chip.&lt;/p&gt;

&lt;p&gt;The compiled Doom code and static data contribute on the order of another 330 KB to the firmware, which is about 12 percent of the current firmware image, or about 8 percent of one OTA app slot. At runtime, Doom's zone allocator reserves 2 MB of PSRAM before you count the screen buffers, luma buffer, and OLED buffer. It also uses a 24 KB internal SRAM stack for its own FreeRTOS task. FreeRTOS is the little real-time operating system under the firmware; a task is its version of a thread.&lt;/p&gt;

&lt;p&gt;And yes, we had to leave room for OTA. That was the part of the memory map that made the flash budget feel much less like "16 MB" and much more like "two carefully guarded parking spaces." The production partition table has two app slots, each about 3.875 MB, so the badge can keep the currently running firmware while writing the next firmware image into the other slot. The current firmware binary is about 2.8 MB, which is already around 68 percent of one slot. If the firmware grows past the slot size, OTA does not happen. It does not matter that there is technically flash elsewhere; the update has to fit in the slot.&lt;/p&gt;

&lt;p&gt;This was one of the most difficult parts of the project for our entire team. It forced embedded humility.&lt;/p&gt;

&lt;p&gt;Every allocation felt physical.&lt;/p&gt;

&lt;h2&gt;
  
  
  C++ as the kernel, MicroPython as the place I wanted to play
&lt;/h2&gt;

&lt;p&gt;When I do hardware hacking for myself, I usually reach for MicroPython or CircuitPython. That is the layer where I feel fast and curious instead of precious. I can make an LED blink, read a sensor, draw something on a screen, and keep going before my brain has time to turn the whole thing into a software architecture dissertation.&lt;/p&gt;

&lt;p&gt;So part of the badge goal was selfish in the best way: I wanted a surface where I could contribute comfortably.&lt;/p&gt;

&lt;p&gt;But I also wanted that for everyone else.&lt;/p&gt;

&lt;p&gt;Not everyone who gets excited by a weird piece of hardware wants to become an embedded C developer before they can make it do something personal. The badge needed a low-friction layer for people who wanted to write apps, make tiny games, animate the LEDs, draw on the OLED, use the IMU, trigger haptics, or send IR without rebuilding the firmware.&lt;/p&gt;

&lt;p&gt;That is what MicroPython is for.&lt;/p&gt;

&lt;p&gt;The trick was not "put Python on a badge." The trick was deciding what Python should never be responsible for.&lt;/p&gt;

&lt;p&gt;C++ owns the scary stuff: timing-sensitive hardware, resource ownership, storage safety, recovery paths, native UI, device lifecycle, and the pieces that need to survive a Python script doing something ambitious. MicroPython gets the friendly APIs: draw text, set pixels, read buttons, move a cursor, rumble the haptics, send IR, store app state, write a little game.&lt;/p&gt;

&lt;p&gt;That meant I also had to learn how to expose C++ functions as MicroPython functions. If MicroPython apps were going to feel like they belonged on the badge, they needed to use the same UI primitives as the native firmware: layout helpers, button prompts, icon drawing, storage helpers, input handling, and all the small pieces that make an app feel consistent instead of like a loose script that happened to run on the same hardware.&lt;/p&gt;

&lt;p&gt;I did not expect to spend as much time as I did on glyphs.&lt;/p&gt;

&lt;p&gt;At 128x64 pixels, a button icon is not just decoration. It is either legible or it is dust. We tested version after version of the tiny glyphs for the physical buttons because those shapes were part of the language of the badge. The first version used a Nintendo-style button layout. Then we swapped toward a PlayStation/Xbox convention. Then we discovered that most of us had already internalized the Switch version so deeply that the "more standard" mapping felt wrong, and we changed it back.&lt;/p&gt;

&lt;p&gt;That is firmware work too. Not glamorous, exactly, but it is the difference between a hackable thing and a thing people can actually use.&lt;/p&gt;

&lt;p&gt;For a tiny app, the badge can feel almost casual:&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="nf"&gt;oled_clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;oled_println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, Replay&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;oled_show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;led_override_begin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;led_show_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IMG_HEART&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;led_override_end&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can connect over USB, use JumperIDE, inspect files, run scripts through the REPL, and iterate without treating the firmware like a locked box.&lt;/p&gt;

&lt;p&gt;That is the part I care about most. The C++ runtime makes the badge stable enough to trust. MicroPython makes it personal enough to love.&lt;/p&gt;

&lt;h2&gt;
  
  
  Games, Doom, and hidden delight
&lt;/h2&gt;

&lt;p&gt;The games were partly technical demos, partly love letters to weird handheld games, and partly a way to hide little moments of surprise inside the badge.&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%2Fimages.ctfassets.net%2F0uuz8ydxyd9p%2F3nFsgy0PSrAkuC0Sn1VnLW%2F1cee877e6d9e6ad8461fa83fecc43893%2Fattendees-using-badges.jpg" 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%2Fimages.ctfassets.net%2F0uuz8ydxyd9p%2F3nFsgy0PSrAkuC0Sn1VnLW%2F1cee877e6d9e6ad8461fa83fecc43893%2Fattendees-using-badges.jpg" alt="attendees-using-badges" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Replay attendees doing exactly what I hoped they would! Playing with the badges&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I grew up loving Nintendo DS games that treated hardware constraints as creative prompts instead of limitations. Games like &lt;em&gt;Henry Hatsworth&lt;/em&gt; and &lt;em&gt;The World Ends with You&lt;/em&gt; were always doing something strange with two screens, odd controls, timing, and divided attention. The device shaped the play.&lt;/p&gt;

&lt;p&gt;So once the badge had an OLED screen, joystick, buttons, accelerometer, haptics, and an LED matrix, my brain went directly to "what can this do that a normal screen cannot?"&lt;/p&gt;

&lt;p&gt;That is how you end up with Flappy Asteroids: Flappy Bird on the lower LED matrix, Asteroids on the top OLED, and absolutely no mercy anywhere.&lt;/p&gt;

&lt;p&gt;The current record is 27 seconds, which I understand as a number but not as a human achievement. I made the thing and I cannot reliably survive five.&lt;/p&gt;

&lt;p&gt;Breaksnake, IR Block Battle, Synth, the drawing tools, the LED toys, the community apps — they all served the same purpose. They made the badge feel discoverable. I wanted people to keep finding things. A menu item. A hidden interaction. A weird little game. Something that made them turn to the person next to them and say, "wait, this does what?"&lt;/p&gt;

&lt;p&gt;And then there is Doom.&lt;/p&gt;

&lt;p&gt;Doom is funny because getting Doom running on strange hardware is almost a genre of engineering by itself. It is a joke, but it is also a test. Can the device handle input ownership? Can it render fast enough? Can it allocate the buffers? Can it pause the normal badge services and restore them afterward? Can the partition layout hold the WAD? Can release tooling distribute the pieces correctly?&lt;/p&gt;

&lt;p&gt;On the badge, Doom runs as a guest mode. It takes over the screen and input, uses PSRAM for large buffers, renders a 160x100 view down to the 128x64 monochrome OLED, drives the LED matrix as part of the HUD, and then has to cleanly hand the device back when you are done.&lt;/p&gt;

&lt;p&gt;That forced architectural honesty. Resource ownership stopped being theoretical once a 1993 game wanted to borrow the entire badge.&lt;/p&gt;

&lt;p&gt;I highly recommend adding a ridiculous feature to your embedded project if you want to find out whether your abstractions are real.&lt;/p&gt;

&lt;h2&gt;
  
  
  Temporal flashing Temporal badges
&lt;/h2&gt;

&lt;p&gt;Flashing one badge is a command.&lt;/p&gt;

&lt;p&gt;Flashing thousands of badges is a distributed systems problem wearing a USB cable.&lt;/p&gt;

&lt;p&gt;At some point, manually flashing badges stops being a developer workflow and starts being a production line. You need to detect devices, build firmware, prepare the filesystem image, flash in parallel, retry failures, verify boot, sync clocks, keep logs, and know which unit failed without squinting at a dozen terminal windows.&lt;/p&gt;

&lt;p&gt;So I built Ignition.&lt;/p&gt;

&lt;p&gt;Ignition is the Temporal-powered flashing system in the public repo. It runs locally, no cloud required, and uses Temporal workflows to orchestrate the build and flash pipeline. It can download the latest factory image from GitHub Releases, detect connected badges over USB, start child workflows per badge, flash firmware and apps in parallel, verify the badge booted, sync the clock, and show progress through workflow history and activity heartbeats.&lt;/p&gt;

&lt;p&gt;The point was not that existing factory flashing tools are bad. The factory already has tools that can write bytes to chips very efficiently. Ignition wrapped that kind of work in a software-shaped control plane: retries, per-badge state, logs, boot verification, clock sync, and a visible history of what happened to each physical device. That is the difference between "we flashed a batch" and "we know which badges flashed, which ones failed, why they failed, and where to pick back up."&lt;/p&gt;

&lt;p&gt;In other words, at some point the badge project became Temporal enough that we needed Temporal to ship the Temporal badge.&lt;/p&gt;

&lt;p&gt;Which is objectively too on the nose, but I am choosing to embrace it.&lt;/p&gt;

&lt;p&gt;The funny thing is that this was not just brand poetry. It was useful. Temporal gave us retries, status, history, and a durable mental model for a process that involved physical devices being plugged in and unplugged in batches. The same tool we talk about for long-running software processes turned out to be a pretty good fit for "please flash this table full of 32 tiny computers and tell me which ones survived."&lt;/p&gt;

&lt;p&gt;And now that same tool is in the public repo. You can use it to flash one badge on your desk, which is calmer than what we were doing with it, but spiritually related.&lt;/p&gt;

&lt;h2&gt;
  
  
  The open-source victory lap
&lt;/h2&gt;

&lt;p&gt;Getting the badge ready for open source was the victory lap after shipping these crazy things.&lt;/p&gt;

&lt;p&gt;It was also a second engineering project.&lt;/p&gt;

&lt;p&gt;Locally on my machine, this project lived in what I called the omni directory, which is exactly as ominous as it sounds. I have no idea how anyone else structured their copy of the work, but mine had firmware repos, hardware repos, prototype repos, QA firmware, flashing tools, event tooling, design assets, worktrees, specs, agent reports, old branches, website QA code, and the printing software I had to build for our badge generator all piled into one place.&lt;/p&gt;

&lt;p&gt;Some of that belonged in the public artifact. Some of it very much did not. Some of it was useful context. Some of it was archaeology. Some of it was a reminder that every ambitious project creates a wake behind it.&lt;/p&gt;

&lt;p&gt;So open sourcing the badge meant curating.&lt;/p&gt;

&lt;p&gt;We had to remove private and event-only context. We had to make the docs usable. We had to separate source from generated artifacts. We had to package release images. We had to publish the hardware files in a form that was useful without dumping every temporary file a design tool had ever breathed on. We had to preserve licensing and third-party notices, especially because Doom brings its own legal furniture. We had to create a Community Apps path, contribution rules, and review checks so the badge could grow without turning into a tiny chaos machine.&lt;/p&gt;

&lt;p&gt;We also had to decide what future contributors, human or AI, actually need to know.&lt;/p&gt;

&lt;p&gt;One underrated part of the cleanup was producing a small memory layer for future agents. Not the whole project history. Not every false start. Not every conversation. Just enough context to know where the important code lives, which repos matter, what the architectural boundaries are, what old branches mean, and which parts should be treated as historical sediment instead of active direction.&lt;/p&gt;

&lt;p&gt;That feels like a very 2026 kind of maintainability problem.&lt;/p&gt;

&lt;p&gt;Open source is no longer only about code being available. It is about whether the next person, or the next coding agent helping that person, can arrive in the project and become useful before they get overwhelmed. As my colleague &lt;a href="https://temporal.io/blog/a-mental-model-for-agentic-ai-applications" rel="noopener noreferrer"&gt;Cornelia Davis&lt;/a&gt; put it in a line that has been rattling around in my head for months: "We are no longer just teaching humans, we are teaching agents now."&lt;/p&gt;

&lt;p&gt;The public repo is not the whole messy garage. It is the workbench after we cleaned it enough that someone else could actually build something there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second life of the badge
&lt;/h2&gt;

&lt;p&gt;The badge did its job at Replay if it made people smile.&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%2Fimages.ctfassets.net%2F0uuz8ydxyd9p%2F48uTDQVBHsjdMzMr01ClyF%2F75cb0c7d7c66ca82896f02a03694a028%2Fbadges-in-the-wild.jpg" 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%2Fimages.ctfassets.net%2F0uuz8ydxyd9p%2F48uTDQVBHsjdMzMr01ClyF%2F75cb0c7d7c66ca82896f02a03694a028%2Fbadges-in-the-wild.jpg" alt="badges-in-the-wild" width="799" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;And something that made me smile — after all the hard work, seeing the badges in the wild hanging around attendees necks&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It keeps doing its job if people keep changing it.&lt;/p&gt;

&lt;p&gt;That was the whole point. We did not want to make 2,000 objects that felt magical for three days and then became drawer fossils. The badge has a second life because the firmware is open, the hardware files are there, the docs are readable, MicroPython is embedded, and the community app path exists.&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%2Fapkxoiv6gzpszwjvt9ug.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%2Fapkxoiv6gzpszwjvt9ug.png" alt="badge.temporal.io" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Badge.temporal.io&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can reflash it. You can write an app. You can install community apps. You can inspect the hardware. You can try to manufacture one if you are feeling brave. You can make the LED matrix do something wonderful or cursed. You can build a tiny game. You can make an IR remote. You can find some corner of the device we did not fully explore and make it yours.&lt;/p&gt;

&lt;p&gt;If the first life of the badge was surprise, I hope the second life is mischief.&lt;/p&gt;




&lt;p&gt;Everything you need is at &lt;a href="https://badge.temporal.io" rel="noopener noreferrer"&gt;badge.temporal.io&lt;/a&gt; and all the code is up on &lt;a href="https://github.com/temporal-community/badge.temporal.io" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; ready for you to clone.&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%2Fbthnpmjd3t460vabivlb.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%2Fbthnpmjd3t460vabivlb.png" alt="github.com/temporal-community/badge.temporal.io" width="799" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;github.com/temporal-community/badge.temporal.io&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We have also made updates since Replay, so even if your badge worked perfectly at the conference, you will want to update your firmware.&lt;/p&gt;

&lt;p&gt;Flash it, fork it, write an app, inspect the hardware files, submit a PR with a Community App or a fix, or send us something weird enough that I have to stop what I am doing and try it. &lt;strong&gt;You can submit any cool stuff in this &lt;a href="https://temporalio.slack.com/archives/C04JHEXJC4Q" rel="noopener noreferrer"&gt;Slack&lt;/a&gt; channel.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;We also have a few extras, and we will be sharing them with people at our San Francisco events over the next few months. So if you missed Replay, &lt;em&gt;come find us.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There may still be a tiny hackable computer waiting for you to put your name on it.&lt;/p&gt;

</description>
      <category>temporal</category>
      <category>codesamples</category>
      <category>contributions</category>
      <category>durableexecution</category>
    </item>
    <item>
      <title>Temporal AI Question Planetarium using Temporal Workflows</title>
      <dc:creator>Shy Ruparel</dc:creator>
      <pubDate>Mon, 01 Jun 2026 17:23:38 +0000</pubDate>
      <link>https://dev.to/temporalio/temporal-ai-question-planetarium-using-temporal-workflows-1785</link>
      <guid>https://dev.to/temporalio/temporal-ai-question-planetarium-using-temporal-workflows-1785</guid>
      <description>&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/iizTUZiKPDA"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>temporal</category>
      <category>durableexecution</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Behind The Badge: How We Built 2,000 Hackable Badges For Temporal Replay</title>
      <dc:creator>Shy Ruparel</dc:creator>
      <pubDate>Tue, 26 May 2026 04:00:00 +0000</pubDate>
      <link>https://dev.to/temporalio/behind-the-badge-how-we-built-2000-hackable-badges-for-temporal-replay-ejo</link>
      <guid>https://dev.to/temporalio/behind-the-badge-how-we-built-2000-hackable-badges-for-temporal-replay-ejo</guid>
      <description>&lt;p&gt;At some point in December, Candace, our Head of Design, asked me: what if your time at Replay could be represented as a Workflow? Every check-in, every fun event, every person you met, a living timeline, running on Temporal.&lt;/p&gt;

&lt;p&gt;Four months later, I'm coordinating manufacturing circuit board badges in Shenzhen over WhatsApp at 11 p.m., and I've hand-soldered more PCBs than I'd like to admit.&lt;/p&gt;

&lt;p&gt;To understand why I was asked to figure out this project is to know that my role at Temporal has this informal understanding baked into it: give Shy the weird projects. The ones that involve breadboards on a kitchen table at 11 p.m., a &lt;a href="https://www.youtube.com/watch?v=C_fE8T-DwiU" rel="noopener noreferrer"&gt;document translation pipeline for toys&lt;/a&gt;, or a half-baked idea (or two, or three...) that could either be really cool or a complete disaster at a conference in front of 2,000 people.&lt;/p&gt;

&lt;p&gt;The latter is this project where I set out to create badges for Temporal's annual developer conference, &lt;a href="http://replay.temporal.io" rel="noopener noreferrer"&gt;Replay&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Concept Stage
&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%2Fimages.ctfassets.net%2F0uuz8ydxyd9p%2F5300nhqLn62wcqZYu5IeCj%2F807787f838daa7450f2996453ae423b6%2FP1000081.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%2Fimages.ctfassets.net%2F0uuz8ydxyd9p%2F5300nhqLn62wcqZYu5IeCj%2F807787f838daa7450f2996453ae423b6%2FP1000081.png" alt="Elecrow QAing the Replay badge during manufacturing" width="800" height="616"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hit the ground running and started spec'ing it out. I could make one badge by myself, but to scale up to make a badge for everyone at Replay I knew I would need help. Through an introduction from some fellow Recurse Center alum, I found a hardware consultant and we put together a proposal.&lt;/p&gt;

&lt;p&gt;Next, I had to pitch that proposal to Andrew Baker, our VP of Developer Relations, during his first five days on the job. Tricky because he didn't know me at all, but lucky for me, he trusted some DevRel guy with a hardware project budget that required convoluted manufacturing.&lt;/p&gt;

&lt;p&gt;The early concept drawings are genuinely charming to look at now.&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%2F8qk4m43362o376zjl32g.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%2F8qk4m43362o376zjl32g.png" alt="Early badge concept sketch showing a chunky handheld device" width="700" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our hardware consultant's early sketches are full of hand-drawn illustrations of attendees holding a chunky handheld device with a T9 keyboard, an e-paper name tag on the front, a kickstand so it could sit on a table. There's even a little Tamagotchi-esque character on the screen. The vision at that stage was something closer to a personal game console, or honestly a weird cell phone that only worked at Replay.&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%2Fimages.ctfassets.net%2F0uuz8ydxyd9p%2FTSchEqORfXgaVloPgEPFc%2F5d013338c29a8f8cf776508837235ed5%2FScreenshot_2026-04-29_at_2.38.02%25C3%25A2__PM.png%3Fw%3D700" 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%2Fimages.ctfassets.net%2F0uuz8ydxyd9p%2FTSchEqORfXgaVloPgEPFc%2F5d013338c29a8f8cf776508837235ed5%2FScreenshot_2026-04-29_at_2.38.02%25C3%25A2__PM.png%3Fw%3D700" alt="Hand-drawn illustration of Replay attendees gathered around a glowing badge" width="463" height="718"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then on to artistic inspiration. Our consultant came back with these stunning hand-drawn illustrations. It captures the spirit of what we were going for better than any spec doc ever could: it shows attendees gathered together, shrouded in a collective glow, illuminated by the feeling of knowledge and community. I'd even say the energy is a little cosmic.&lt;/p&gt;

&lt;p&gt;This is just the type of energy we captured for the event, as Replay is the community's conference. We just get the privilege of paying for it. Finding ways for attendees to interact, share moments, learn from each other, and spark something in each other is always the actual goal. The badge is the vehicle taking you there.&lt;/p&gt;

&lt;h2&gt;
  
  
  What The Badge Does
&lt;/h2&gt;

&lt;p&gt;When you hear "badge," you probably picture a rigid little plastic thing with your name and an outdated LinkedIn headshot on it. Scrap that entire notion when we're talking about this badge because this one is more like a game piece.&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%2Fjrny7fau0wj9adufolwj.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%2Fjrny7fau0wj9adufolwj.png" alt="Early prototype badge held in hand with the screen showing attendee information" width="700" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Attendees navigate Replay with a mission of gathering with their peers to traverse the cosmos of the development unknown. Along the way, they collect connections, unlock new levels, and even find hidden treasures, all while the badge handles the technical complexity involved underneath.&lt;/p&gt;

&lt;p&gt;So what's actually inside it?&lt;/p&gt;

&lt;p&gt;The badge runs on an ESP32-S3 microcontroller with a 128x64 pixel OLED screen, an IR transmitter and receiver, a joystick, buttons, haptic feedback via a vibration motor, and an LED matrix with programmable colors.&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%2F27c4t6wd7tol9ixrhnc7.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%2F27c4t6wd7tol9ixrhnc7.png" alt="Prototype B v0.0 schematic render" width="638" height="794"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It also has a gyroscope, so the screen reorients depending on how you're holding it. Firmware is written in C, with a MicroPython layer sitting on top so attendees can write their own apps without needing to learn anything new. Attendees are able to update their contact information by using the MicroPython REPL and then beam it over to each other with the IR transmitter. The conference schedule is right there on the badge so you don't have to download an app. I don't know about you, but I've encountered very few conference apps that I've liked.&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%2F7ejbsug2k04xr1zyltg9.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%2F7ejbsug2k04xr1zyltg9.png" alt="Green PCB with joystick and buttons held at a factory in Shenzhen" width="800" height="1422"&gt;&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%2Fhb2zs5oxwogei8t1tqi8.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%2Fhb2zs5oxwogei8t1tqi8.png" alt="Temporal-branded bare PCB with ESP32 chip" width="523" height="451"&gt;&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%2Fg7xqwzdvp02n8sx9ursq.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%2Fg7xqwzdvp02n8sx9ursq.png" alt="DELTA badge dev kit revision" width="800" height="1066"&gt;&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%2Fknzrc7zpb96sqtp5rthn.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%2Fknzrc7zpb96sqtp5rthn.png" alt="Badge dev kit components laid out on a table" width="800" height="600"&gt;&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%2Fy2lsgihm55z76tlw34xp.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%2Fy2lsgihm55z76tlw34xp.png" alt="Final black badge render with space motif and blank screen" width="799" height="441"&gt;&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%2F0g52uou3swxe1qz81tbs.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%2F0g52uou3swxe1qz81tbs.png" alt="Final Replay badge render with physical print card attached" width="800" height="1003"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And this is what it became after Kathy, our Senior Brand Designer, got involved. The back of the badge has Ziggy, our tardigrade mascot, as an astronaut, which I love, etched into a PCB.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Of It All
&lt;/h2&gt;

&lt;p&gt;I have to tell you something, and I want you to keep it just between us... I never wrote firmware code before this project. Like ever.&lt;/p&gt;

&lt;p&gt;Now, I'm a decent enough programmer and I know how to have an architecture conversation with the best of 'em. I can confidently identify when something is going wrong and I know the right questions to ask to reveal a solution, but C firmware for an embedded microcontroller wasn't something I had ever done professionally.&lt;/p&gt;

&lt;p&gt;My testimony is this: generative AI made this project possible for me!&lt;/p&gt;

&lt;p&gt;I used Claude and Claude Code extensively throughout my development process, eventually swapping to Codex as newer models became available. The way I think about it: I became a full-time architect with access to a team of very high int, low wisdom junior engineers who are great at implementation but need a lot of hand-holding. You can't give them something vague. You have to know what you want, know when they've gotten it wrong, and stay deliberate about what you let them build. I genuinely don't think this workflow would have worked without my ten-plus years of developer experience behind it because the experience tells you which questions matter and when to push back.&lt;/p&gt;

&lt;p&gt;The codebase ended up around 5,000 lines of production logic, with tests more than quadrupling that. I used those tests as a way to validate what the AI was building and build trust in the output. I would record and review Playwright tests to confirm user interactions worked the way I wanted them to. I'll admit, when I code without AI, I mostly skipped writing tests in all my previous work. For me to have trust in the code that was getting generated, I needed extremely solid test coverage from the start.&lt;/p&gt;

&lt;p&gt;The documentation from all the architecture debates I had with the AI, the back-and-forth about structure, what to build, and what to cut, is over &lt;strong&gt;21,000&lt;/strong&gt; &lt;em&gt;lines&lt;/em&gt;, managed through &lt;a href="https://github.com/github/spec-kit" rel="noopener noreferrer"&gt;GitHub Spec Kit&lt;/a&gt; that tracked specs and architecture decisions as the project evolved. That ratio says something interesting about what the job actually looks like now. It was very out of my comfort zone as someone with a CS degree and a full career without any AI tools even existing. As this project took four months, I swapped my tooling quite a bit. I finalized the project leveraging Codex with the &lt;a href="https://skills.sh/mattpocock/skills/grill-me" rel="noopener noreferrer"&gt;Grill Me Skill&lt;/a&gt;. It was a lot more reminiscent of wordsmithing an English essay than anything hands-on-keyboard, which I'm still adjusting to.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Something for you to look forward to: the whole codebase is open source! That means you'll be able to see the output of every argument I had with AI to get here! &lt;em&gt;and the crowd goes wild&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  From One To Two Thousand
&lt;/h2&gt;

&lt;p&gt;Now, I've built weird IoT stuff before. In fact, that's kind of my whole thing. But I've never had to build 2,000 of the same thing, and it turns out those are completely different problems.&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%2Fqfmn64mn7lg9aognhqll.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%2Fqfmn64mn7lg9aognhqll.png" alt="DEF CON cat-shaped badge" width="700" height="527"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The original inspiration for this whole category of badge, ours, Twilio's, GitHub's, traces back to &lt;a href="https://defcon.org/" rel="noopener noreferrer"&gt;DEF CON&lt;/a&gt;, the hacker conference in Las Vegas. They've been doing &lt;a href="https://www.defcon.org/html/links/dc-badge.html" rel="noopener noreferrer"&gt;programmatic badges&lt;/a&gt; for over twenty years. The badges run cryptography puzzles, communicate with each other, and unlock hidden games. Last year's badge had a full Pokemon clone where you could walk around a virtual version of the convention center. The level of engineering that community pours into these things is genuinely absurd in the best way, and I have a lot of love for it. I used to go when I was back in college during the era of Ryan "LostboY/1o57" Clarke making all the conference badges, and my memories here helped guide a lot of the creative energy I poured into this project.&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%2F3hvygiau8avj5t9wewom.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%2F3hvygiau8avj5t9wewom.png" alt="Pimoroni Badgeware and a Badger 2040" width="800" height="603"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I had a lot of guidance too. &lt;a href="https://pimoroni.com/" rel="noopener noreferrer"&gt;Pimoroni&lt;/a&gt;, the team behind the GitHub Universe badge, were also incredibly generous. They couldn't meet our timeline but they sat with me anyway and gave me advice, so this is a huge shout out to them. Their work inspired me and the updated &lt;a href="https://badgewa.re/" rel="noopener noreferrer"&gt;Badgeware&lt;/a&gt; ecosystem served as my prototyping platform to prove that I could pull this off. Their contributions helped us get 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%2Fxcjrjd6gn2djz7i0297i.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%2Fxcjrjd6gn2djz7i0297i.png" alt="Multiple dev badges plugged in and running simultaneously" width="431" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the manufacturing side: I knew, at least in an abstract way, that hardware companies manage factory relationships through WhatsApp and that most electronics manufacturing happens in Shenzhen. Actually living that is something else. I'd start getting messages from the factory at 10 p.m. I'd answer until I fell asleep. I'd wake up and our west coast based hardware consultant had been chatting with them for another four hours on top of that. You're always catching up, always a little behind.&lt;/p&gt;

&lt;p&gt;Our manufacturer, &lt;a href="https://www.elecrow.com" rel="noopener noreferrer"&gt;Elecrow&lt;/a&gt;, was incredibly generous with their time and expertise, helping us solve the problems that came up throughout this entire process. I'm especially grateful to our account manager, Chris, who not only stepped up to make sure everything worked, arrived safely, and showed up on time, but also helped us get the most out of our time in Shenzhen. She did double duty, guiding us through all the electronics markets Shenzhen is known for.&lt;/p&gt;

&lt;p&gt;Sourcing LiPo batteries in bulk is harder than you'd think because you can't air ship them, so everything goes ground, and vendors get suspicious when you try to order large quantities at once. This resulted in our dev kits running off triple A batteries. Getting enough screens for the dev kits turned into placing about 30 separate Amazon orders across different sellers to work around quantity throttling. Our accounting team is going to have feelings about that for a while... sorry guys.&lt;/p&gt;

&lt;p&gt;There's also the China trip. Temporal's security policy doesn't allow company compute into the country, which is reasonable, so when I needed to go supervise manufacturing, it required multi-week negotiations between HR, security, and finance to work out.&lt;/p&gt;

&lt;p&gt;The answer turned out to be a clean MacBook and a clean phone provisioned specifically for the trip, access to exactly one Slack channel, and no corporate accounts. I had to say goodbye to Claude for the duration, which means I had to go back to the ways of ye olden days, pre-2023, for the development, which definitely made the firmware work interesting.&lt;/p&gt;

&lt;p&gt;That setup was the right call, but it did mean that once I was in China, I was mostly on my own for anything that came up. Between the limited access, the timezone gap, and the fact that problems were being discovered in real time on the manufacturing floor, there was not much opportunity to phone a friend. I got one hour-long call to get help on a specific issue, and otherwise the last-minute problems had to be debugged with whatever I had with me in the moment.&lt;/p&gt;

&lt;p&gt;Meanwhile, my single allowed Slack channel basically turned into a mini travel blog: factory floors, production lines, electronics markets, and a steady stream of "look at this, manufacturing is magic and also chaos." It was useful for keeping people in the loop, but it was not exactly the same thing as having the full company brain available while trying very hard not to become the bottleneck between "we found a problem" and "thousands of these need to ship."&lt;/p&gt;

&lt;p&gt;Here are a few photos of what went down on my trip.&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%2Frg4icunarpy2qbbrb8bw.jpg" 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%2Frg4icunarpy2qbbrb8bw.jpg" alt="Factory floor with hands working on PCBs in a jig" width="800" height="451"&gt;&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%2Fruozzmwalwxuwwyn0x03.jpg" 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%2Fruozzmwalwxuwwyn0x03.jpg" alt="Stack of manufactured PCBs racked up at the factory" width="800" height="451"&gt;&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%2Fpj37gq7bka6ymxrnxdp4.jpg" 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%2Fpj37gq7bka6ymxrnxdp4.jpg" alt="Factory machine with PCBs on an assembly line" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, I'm back, the badges were in tow, and I was excited to get them in your hands at Replay.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's In Your Hands At Replay! And Beyond
&lt;/h2&gt;

&lt;p&gt;We live in a hyper-consumerist world, and we don't want to pile on to that by creating a bunch of e-waste.&lt;/p&gt;

&lt;p&gt;So the badge isn't e-waste when the conference ends. That was a hard constraint from the start: we're not building 2,000 things that get thrown in a drawer after three days. Because the firmware is open source and MicroPython is embedded, you can keep hacking on yours after Replay.&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%2Fimages.ctfassets.net%2F0uuz8ydxyd9p%2F6TgOcYwpjhj3jnfBWZr9iM%2F6f67d39262846d6e60aac83113d9322e%2FCH5_8602.jpg" 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%2Fimages.ctfassets.net%2F0uuz8ydxyd9p%2F6TgOcYwpjhj3jnfBWZr9iM%2F6f67d39262846d6e60aac83113d9322e%2FCH5_8602.jpg" alt="Rows of finished Replay badges ready to be handed out" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sky's the limit really. The IR sensor means you could build a universal remote, the screen and joystick give you a tiny game device, and it'll keep being whatever you make it.&lt;/p&gt;

&lt;p&gt;People also started hacking on the firmware almost immediately. One attendee built a full Tamagotchi game during the conference. That's exactly the kind of thing we hoped for: someone picking up the hardware and immediately thinking &lt;em&gt;cool, what can I make this do?&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%2Fimages.ctfassets.net%2F0uuz8ydxyd9p%2F4gVtwY9sQkYoDy1A9oPDZU%2F5a162eb2bc010d1bf120ea4b08ba8962%2FCH5_8753.jpg" 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%2Fimages.ctfassets.net%2F0uuz8ydxyd9p%2F4gVtwY9sQkYoDy1A9oPDZU%2F5a162eb2bc010d1bf120ea4b08ba8962%2FCH5_8753.jpg" alt="Replay attendees exploring the badges" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For next year, I want to shift from the ESP32-S3 to the Raspberry Pi RP2350 and have every badge function as a Temporal worker. We managed to get &lt;a href="https://docs.temporal.io/develop/rust/" rel="noopener noreferrer"&gt;Temporal's Rust SDK&lt;/a&gt; running on the badge thanks to Edward Amsden, Staff Software Engineer on the SDK Language Runtime team, who saw me demo the badge during an engineering sprint showcase in his first week at Temporal and then casually spent the weekend hacking together Rust SDK support for us. I more or less seconded him to the badge team for his first month with us so we could get the project over the finish line. SDK team, I promise you can have him back now that we're finished with Replay. Because Edward got Temporal running in the final weeks of the project, we didn't have enough time to really showcase what we could do with it. Next year, though: two thousand workers hanging around people's necks in the same room. I don't know what I'd run on them yet, but I very much want to find out.&lt;/p&gt;

&lt;p&gt;We also made a bunch of deeply weird games for the badge, because apparently once you put an OLED screen, joystick, accelerometer, LED matrix, Wi-Fi, and a questionable amount of ambition into something people wear around their necks, then put a person who spent far too much time playing The World Ends with You and Henry Hatsworth on the Nintendo DS in their youth in charge of the project, aka me, this is what happens. Some are real games, some are firmware tests that got out of hand, and at least one is Flappy Asteroids: Flappy Bird on the lower LED matrix, Asteroids on the top, and absolutely no mercy anywhere. If you survived for more than 30 seconds at Replay, I hope you got your special variant joystick cap. And yes, we got Doom running.&lt;/p&gt;

&lt;p&gt;2,000+ badges showed up and worked. If you want to get hands-on with yours, everything you need is at &lt;a href="https://badge.temporal.io/" rel="noopener noreferrer"&gt;badge.temporal.io&lt;/a&gt;, and if you missed out this year, make sure you come to one of our SF events over the next few months and attend Replay 2027!&lt;/p&gt;

</description>
      <category>temporal</category>
      <category>hardware</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Why I needed Durable Execution to Read a Toy Manual</title>
      <dc:creator>Shy Ruparel</dc:creator>
      <pubDate>Fri, 10 Apr 2026 15:53:01 +0000</pubDate>
      <link>https://dev.to/temporalio/why-i-needed-durable-execution-to-read-a-toy-manual-35cn</link>
      <guid>https://dev.to/temporalio/why-i-needed-durable-execution-to-read-a-toy-manual-35cn</guid>
      <description>&lt;p&gt;Watch me take a Japanese toy manual and turn its translation into a bulletproof, &lt;strong&gt;AI-powered ETL pipeline&lt;/strong&gt;. I’ll show you how I use &lt;strong&gt;Temporal Workflows&lt;/strong&gt; to guarantee an AI pipeline never loses progress, surviving network failures, API crashes, and more.&lt;/p&gt;




&lt;h3&gt;
  
  
  What You'll Learn
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Why Spider-man is the reason that Power Rangers has a giant robot.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guaranteed Completion with Temporal:&lt;/strong&gt; I’ll show you how to ensure your code keeps running even if servers crash or APIs fail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallel OCR &amp;amp; Translation:&lt;/strong&gt; Learn how I used a &lt;strong&gt;"fan-out" pattern&lt;/strong&gt; with Google Document AI to process a 20-page manual in &lt;strong&gt;50 seconds&lt;/strong&gt; instead of 10 minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resilient AI Cleanup:&lt;/strong&gt; See how I use &lt;strong&gt;Pydantic&lt;/strong&gt; and &lt;strong&gt;Temporal&lt;/strong&gt; together to handle non-deterministic LLM outputs from Gemini and automatically retry failed validations.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Ready to build it yourself?&lt;/strong&gt; 👉 &lt;a href="https://temporal.io/code-exchange/toku-solutions" rel="noopener noreferrer"&gt;Check out the code here!&lt;/a&gt;&lt;/p&gt;

</description>
      <category>temporal</category>
      <category>supersentai</category>
      <category>kamenrider</category>
      <category>ai</category>
    </item>
    <item>
      <title>Tracking Global Vaccination Rates with Docker, Python, and IoT</title>
      <dc:creator>Shy Ruparel</dc:creator>
      <pubDate>Tue, 29 Mar 2022 17:19:09 +0000</pubDate>
      <link>https://dev.to/docker/tracking-global-vaccination-rates-with-docker-python-and-iot-3007</link>
      <guid>https://dev.to/docker/tracking-global-vaccination-rates-with-docker-python-and-iot-3007</guid>
      <description>&lt;p&gt;I'm a major fan of the ESP32-S2 platform of low-power, low-cost, Wi-Fi capable microcontrollers. Adafruit specifically sells a variant that features a 2.9' E-ink display and Python support, called &lt;a href="https://www.adafruit.com/product/4800" rel="noopener noreferrer"&gt;the MagTag&lt;/a&gt;. I'm not ashamed to admit I have &lt;em&gt;far&lt;/em&gt; too many of these things littered across my apartment. &lt;/p&gt;

&lt;p&gt;One of my favorite organizations is &lt;a href="https://ourworldindata.org/" rel="noopener noreferrer"&gt;Our World in Data (OWID)&lt;/a&gt;. They work with thousands of researchers to make research and data accessible to the public. In particular, I've been closely watching &lt;a href="https://github.com/owid/covid-19-data" rel="noopener noreferrer"&gt;their repo&lt;/a&gt; that tracks COVID vaccination rates. As numerous worldwide agencies update their vaccination rate metrics, OWID consolidates that data into a single JSON file. &lt;/p&gt;

&lt;p&gt;The combination of the MagTag and OWID's data set inspired my first project as the newest member of Docker’s DevRel team. I decided to turn the MagTag into a display for the specific OWID I’m following. However, I ran into a few problems while getting started. &lt;/p&gt;

&lt;p&gt;OWID uploads its JSON data collection to GitHub as a raw JSON (or CSV) file, rather than serving it over an API endpoint. The total payload is also 34MB. While that doesn't seem huge, the MagTag itself has just 4MB of flash storage and 2MB of PSRam. That's nowhere near enough memory to parse all that data. Luckily, I commandeered a few spare Raspberry Pi's to create a data processing layer. Using Python, I’m able to spin up a quick Flask server to process the image—and because of Docker, I can deploy a containerized service to my Raspberry Pi in just a few seconds. &lt;/p&gt;

&lt;p&gt;My Pi pulls an image built for Armv7 from Docker Hub. That container creates an API endpoint that’s accessible on my local network. From there, the MagTag can request a country's data from the Pi. The Pi grabs the most recent JSON file from OWID, pulls out just the information the MagTag is looking for, and then passes it along. This method reduces the data payload from 34MB down to the KB range, which is much more manageable for an IoT device.&lt;/p&gt;

&lt;p&gt;That said, let’s dive into the code. &lt;/p&gt;

&lt;h2&gt;
  
  
  Coding Your Solution
&lt;/h2&gt;

&lt;p&gt;If you'd rather just read the code, head over to &lt;a href="https://github.com/Shy/Docker-MagTag-OWID/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; or &lt;a href="https://hub.docker.com/r/shyruparel/flask-owid-parser" rel="noopener noreferrer"&gt;Docker Hub&lt;/a&gt; for deployment instructions. &lt;/p&gt;

&lt;h3&gt;
  
  
  Data processing
&lt;/h3&gt;

&lt;p&gt;Knowing that the Raspberry Pi’s data processing function would be pretty lightweight, I decided to spin up a Flask server—taking advantage of Flask's built-in ability to handle JSON with jsonify. The requests library for grabbing our data from OWID was also pretty handy:&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;# Setup a health route to check if the service is up
&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="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;/health&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;hello&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;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;status&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;api online&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;status_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Secondly, and more importantly, is your route to hit the OWID dataset. This route parses the 34MB JSON file for the requested ISO code, retrieves the most recent data, and formats it for the Raspberry Pi:&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;# Setup a GET route for requesting the data of the requested ISO Code.
&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;/iso_data/&amp;lt;iso&amp;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;GET&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;iso_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iso&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Get the OWID Data from GitHub
&lt;/span&gt;    &lt;span class="n"&gt;vaccination_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://raw.githubusercontent.com/owid/covid-19-data/master/public/data/vaccinations/vaccinations.json&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="n"&gt;vaccinations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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="n"&gt;vaccination_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;False&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;vaccinations_dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="c1"&gt;# Using lambda and filter to find our requested ISO Code
&lt;/span&gt;    &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iso_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;iso&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vaccinations&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="c1"&gt;# If data is found format it for our response.
&lt;/span&gt;        &lt;span class="n"&gt;vaccinations_dict&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;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iso_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iso_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OWID_&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; &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;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&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;response&lt;/span&gt; &lt;span class="o"&gt;=&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;vaccinations_dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Returning a 203 since it's a mirror of the OWID data.
&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;status_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;203&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;# If no data is found return a 404.
&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;jsonify&lt;/span&gt;&lt;span class="p"&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;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&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;not found&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invalid iso code&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, you should get the code to install the dependencies and generate a requirements file. I use &lt;a href="https://github.com/pyenv/pyenv-virtualenv" rel="noopener noreferrer"&gt;pyenv&lt;/a&gt; to manage my Python virtual environments, but you substitute that with your preferred method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pyenv virtualenv 3.10.1 flask-owid-server
pyenv activate flask-owid-server
pip &lt;span class="nb"&gt;install &lt;/span&gt;flask requests
pip freeze &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; requirements.txt
flask run
&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%2F2uplsk8wzkaqg2c8obee.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%2F2uplsk8wzkaqg2c8obee.png" alt="Screenshot of code running in the terminal." width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, open a new shell prompt and make a curl request to the Flask server, just to confirm that everything is running as expected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl localhost:5000
curl localhost:5000/iso_data/USA
&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%2Fb353p2rgkwo099oypz4h.webp" 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%2Fb353p2rgkwo099oypz4h.webp" alt="Screenshot of code running in the terminal." width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dockerizing and Deploying the Flask Server
&lt;/h3&gt;

&lt;p&gt;With a functional Flask server, we can dockerize it and simplify the app-deployment process for the Raspberry Pi. I started by creating a Dockerfile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;#Use the python 3 base image.&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3-slim&lt;/span&gt;
&lt;span class="c"&gt;#Expose ports&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 5000&lt;/span&gt;
&lt;span class="c"&gt;# Setup work directory.&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="c"&gt;# Copy the requirements file to the container and the install dependencies.&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt /app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip3 &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt;
&lt;span class="c"&gt;# Copy all the code into the container/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /app&lt;/span&gt;
&lt;span class="c"&gt;# Spin up our flask server.&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["python3"]&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["app.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this Dockerfile in place, I can run a Docker build command to create my image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;--tag&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;yourdockerusernamehere&lt;span class="o"&gt;}&lt;/span&gt;/flask-owid-parser:latest &lt;span class="nb"&gt;.&lt;/span&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%2Fzb2qo2oznjae6d4oa54u.webp" 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%2Fzb2qo2oznjae6d4oa54u.webp" alt="Screenshot of code running in the terminal." width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can run your newly-created image from Docker Desktop—while making sure you name the container and set the local port to 5000.&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%2Fnuhid12bty5mix1tfqya.webp" 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%2Fnuhid12bty5mix1tfqya.webp" alt="Screenshot of Docker Desktop." width="800" height="567"&gt;&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%2F7slkx7rwz4cv08dfem9s.webp" 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%2F7slkx7rwz4cv08dfem9s.webp" alt="Screenshot of Docker Desktop." width="800" height="567"&gt;&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%2Fkq4b2m30dgvvd2te4c84.webp" 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%2Fkq4b2m30dgvvd2te4c84.webp" alt="Screenshot of Docker Desktop." width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you prefer using the command line, something like Docker CLI can achieve the same effect.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker container run &lt;span class="nt"&gt;--publish&lt;/span&gt; 5000:5000 &lt;span class="nt"&gt;--detach&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; flask-owid-parser &lt;span class="o"&gt;{&lt;/span&gt;yourdockerusernamehere&lt;span class="o"&gt;}&lt;/span&gt;/flask-owid-parser
&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%2Fhfhb4jjmyr5smrm1p13g.webp" 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%2Fhfhb4jjmyr5smrm1p13g.webp" alt="Screenshot of code running in the terminal." width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once again, make a curl request to localhost:5000 to confirm that everything is running correctly.&lt;/p&gt;

&lt;p&gt;With everything verified on your machine, you can build the application to run on a Raspberry Pi. The Raspberry Pi 3 Model B from this example uses the armv7 platform, so I can create a build specifically targeting armv7. By running this build on my MacBook, the build time will be substantially faster than on my Raspberry Pi.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;--platform&lt;/span&gt; linux/arm/v7 &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;yourdockerusernamehere&lt;span class="o"&gt;}&lt;/span&gt;/flask-owid-parser:armv7 &lt;span class="nb"&gt;.&lt;/span&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%2F4c5b3gv7816iqlpi5ryd.webp" 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%2F4c5b3gv7816iqlpi5ryd.webp" alt="Screenshot of code running in the terminal." width="800" height="559"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Back in Docker desktop, you can push your armv7 build to Docker Hub.&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%2F41dz261eauvi1cusdin2.webp" 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%2F41dz261eauvi1cusdin2.webp" alt="Screenshot of Docker Desktop." width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Head over to Docker Hub to confirm the armv7 build is displayed correctly.&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%2Fa4es3as6r80b57c34cjy.webp" 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%2Fa4es3as6r80b57c34cjy.webp" alt="Screenshot of Docker Hub." width="800" height="656"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SSH into your Raspberry Pi to start deploying this code. If you haven't already installed Docker, you can &lt;a href="https://docs.docker.com/engine/install/debian/" rel="noopener noreferrer"&gt;view our documentation&lt;/a&gt; for instructions on getting started with Docker on the Pi. &lt;/p&gt;

&lt;p&gt;Next, pull the armv7 image from Docker Hub and run it on the Raspberry Pi.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull &lt;span class="o"&gt;{&lt;/span&gt;yourdockerusernamehere&lt;span class="o"&gt;}&lt;/span&gt;/flask-owid-parser:armv7
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 5000:5000 &lt;span class="nt"&gt;--name&lt;/span&gt; flask-owid-parser &lt;span class="o"&gt;{&lt;/span&gt;yourdockerusernamehere&lt;span class="o"&gt;}&lt;/span&gt;/flask-owid-parser:armv7
&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%2F0euj94ywm6ehruu2r74r.webp" 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%2F0euj94ywm6ehruu2r74r.webp" alt="Screenshot of code running in the terminal." width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Back on my original MacBook, I can both confirm that the server’s running and that it's accessible from my local network—via one final curl request. The default hostname on a fresh Raspberry Pi OS install is raspberrypi, so any Raspberry Pi running Pi OS automatically responds to raspberrypi.local. If that doesn't work for you, swap raspberrypi.local with your Raspberry Pi’s IP address.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ping &lt;span class="nt"&gt;-c&lt;/span&gt; 3 raspberrypi.local
curl raspberrypi.local:5000
curl raspberrypi.local:5000/iso_data/USA
&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%2Fijs8lnq8bzvswhidg0e3.webp" 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%2Fijs8lnq8bzvswhidg0e3.webp" alt="Screenshot of code running in the terminal." width="800" height="559"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring the MagTag
&lt;/h3&gt;

&lt;p&gt;One of my favorite MagTag features is its Circuit Python support. Forget using pip to install dependencies. Instead, simply copy .mpy onto the MagTag via USB alongside your code. To set up a MagTag for the first time, I'd defer to the &lt;a href="https://learn.adafruit.com/magtag-progress-displays/progressbar-basics" rel="noopener noreferrer"&gt;excellent tutorials&lt;/a&gt; put together by the folks over at Adafruit. &lt;/p&gt;

&lt;p&gt;To install this project on your MagTag, visit the GitHub repo (&lt;a href="https://github.com/Shy/Docker-MagTag-OWID/tree/main/magtag" rel="noopener noreferrer"&gt;https://github.com/Shy/Docker-MagTag-OWID/tree/main/magtag&lt;/a&gt;) containing this project’s code. Drag all code from the MagTag directory onto your MagTag via USB. &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%2Fkgodprhqmk972asf7gs0.webp" 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%2Fkgodprhqmk972asf7gs0.webp" alt="Screenshot of file directory." width="800" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you inspect the &lt;a href="https://github.com/Shy/Docker-MagTag-OWID/blob/main/magtag/code.py" rel="noopener noreferrer"&gt;code.py&lt;/a&gt; file, you'll notice that the MagTag is configured to connect to the Raspberry Pi.&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;# Build our endpoint
&lt;/span&gt;&lt;span class="n"&gt;endpoint_iso_first&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://raspberrypi.local:5000/iso_data/{}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iso_first&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the &lt;a href="https://github.com/Shy/Docker-MagTag-OWID/blob/main/magtag/secrets.py" rel="noopener noreferrer"&gt;secrets.py&lt;/a&gt; file to include the information for your own Wifi network. Ensure that your MagTag and Raspberry Pi share the same network. &lt;/p&gt;

&lt;p&gt;Reboot the MagTag to see the most recent information—posted by the folks at Our World in Data. &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%2Fkyny30li1n6k0gc8lx8d.webp" 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%2Fkyny30li1n6k0gc8lx8d.webp" alt="Photo of running MagTag" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Head over to &lt;a href="https://github.com/Shy/Docker-MagTag-OWID/blob/main/magtag/code.py" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; to find the full source code for this project with dependencies, and links to the image on Docker Hub. First-time Python and Docker users should browse our &lt;a href="https://docs.docker.com/language/python/build-images/?utm_campaign=2022-03-25-magtag-owid-python-tutorial&amp;amp;utm_medium=web-referral&amp;amp;utm_source=blog&amp;amp;utm_content=python-getting-started-docs/" rel="noopener noreferrer"&gt;quick start tutorial&lt;/a&gt; for Python devs.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>python</category>
      <category>esp32</category>
      <category>raspberrypi</category>
    </item>
    <item>
      <title>App hosting with the Contentful App Framework</title>
      <dc:creator>Shy Ruparel</dc:creator>
      <pubDate>Wed, 23 Jun 2021 14:52:04 +0000</pubDate>
      <link>https://dev.to/contentful/app-hosting-with-the-contentful-app-framework-40nh</link>
      <guid>https://dev.to/contentful/app-hosting-with-the-contentful-app-framework-40nh</guid>
      <description>&lt;p&gt;I'm cross-posting this article for my &lt;a href="https://www.contentful.com/blog/author/david-fateh/" rel="noopener noreferrer"&gt;Colleague David Fateh&lt;/a&gt;. Check out the original post over on the &lt;a href="https://www.contentful.com/blog/2021/06/23/app-hosting-contentful-app-framework/" rel="noopener noreferrer"&gt;Contentful Blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When you develop an app for Contentful using the &lt;a href="https://www.contentful.com/app-framework/" rel="noopener noreferrer"&gt;App Framework&lt;/a&gt;, you create a single-page application that runs inside an iframe and shows up in the Contentful UI. Previously, hosting an app on the App Framework was only possible through third-party solutions. Today, we are announcing an easier method to host your app called app hosting!&lt;/p&gt;

&lt;p&gt;App hosting takes the hassle out of finding a hosting provider for your app. We have provided developers with a tool to upload their application in bundles which provides for many great development features.&lt;/p&gt;

&lt;p&gt;An app bundle is your single-page application directory. Each bundle should contain at least an index.html file, which will serve as the main entry point for your application to load. A typical bundle's file tree will look something 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;build
├── index.html
└── static
    ├── css
    │   └── index.css
    └── js
        └── index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where build is your root directory and, inside it, there is an index.html file, along with any assets such as CSS and JavaScript that would get loaded alongside your index.html file.&lt;/p&gt;

&lt;p&gt;While there are many ways to create apps, we provide a quickstart tool, the &lt;a href="https://www.contentful.com/developers/docs/extensibility/app-framework/create-contentful-app/" rel="noopener noreferrer"&gt;create-contentful-app&lt;/a&gt;, which can get you a jump-start on your app. If you aren't familiar with building apps, I'd suggest checking out our &lt;a href="https://www.youtube.com/watch?v=oBJK3dJs-qo" rel="noopener noreferrer"&gt;livestream&lt;/a&gt; where Felix and I build the Contentful &lt;a href="https://www.contentful.com/developers/docs/extensibility/app-framework/tutorial/" rel="noopener noreferrer"&gt;starter tutorial app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, let's take a look at the three easy steps to bundling and hosting your own app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1
&lt;/h3&gt;

&lt;p&gt;If you built your app using the &lt;code&gt;create-contentful-app&lt;/code&gt; or &lt;code&gt;create-react-app&lt;/code&gt;, you can simply run&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 build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you built your app using another tool or from scratch, your directory structure will need a minimum of an &lt;code&gt;index.html&lt;/code&gt; file at the root.&lt;/p&gt;

&lt;p&gt;Again, a simple app directory structure would look something 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;build
├── index.html
└── static
    ├── css
    │   └── index.css
    └── js
        └── index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2
&lt;/h3&gt;

&lt;p&gt;Navigate to your app's &lt;a href="https://www.contentful.com/developers/docs/extensibility/app-framework/app-definition/" rel="noopener noreferrer"&gt;AppDefinition&lt;/a&gt; found in your organization settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3
&lt;/h3&gt;

&lt;p&gt;Toggle the app hosting option found under "Frontend" and drag and drop your build directory into the highlighted area like so:&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%2Fimages.ctfassets.net%2Ffo9twyrwpveg%2F50DJWkOXRrh2J7nzHe3F4b%2Fe9b977d7b96032b21757d23d80941abe%2Fuploading.gif" 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%2Fimages.ctfassets.net%2Ffo9twyrwpveg%2F50DJWkOXRrh2J7nzHe3F4b%2Fe9b977d7b96032b21757d23d80941abe%2Fuploading.gif" alt="A gif showing how to toggle the app hosting option found under " width="560" height="342"&gt;&lt;/a&gt;
"/&amp;gt;&lt;/p&gt;

&lt;p&gt;That's it! Your app is now hosted via Contentful. In the Bundles tab, you can check out multiple build versions and revert to an older version of your app in case you need to roll back changes.&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%2Fshov6jt5m3wmtilua5ww.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%2Fshov6jt5m3wmtilua5ww.png" alt="Screenshot of what app bundles look like in the Contentful app" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While uploading an app is easy, there are some common errors that could occur:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Your app doesn't have an index.html file in the root of the uploaded bundle.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You are uploading a Zip that encapsulates the root directory instead of the content directly. Most of the time, the common fix is to navigate into your root directory, select all your files, and zip it at that level.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your upload succeeds but the app doesn't load or appear in the UI. This could be caused by an error in your app's index.html or the JavaScript that it executes. A look at the developer console should provide some hints.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To learn more about the technical details of app hosting, head over to &lt;a href="https://www.contentful.com/developers/docs/extensibility/app-framework/hosting-an-app/" rel="noopener noreferrer"&gt;our documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Launching app hosting on Contentful is another step forward in our vision of creating an extensible and cohesive set of tools that connect our projects and our content. If you want to chat about what you're working on, feel free to ping me on &lt;a href="https://www.contentful.com/slack/" rel="noopener noreferrer"&gt;our community Slack&lt;/a&gt; where I work with fellow developers as we take steps to make our App Framework more accessible and versatile for everyone.&lt;/p&gt;

</description>
      <category>contentful</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to build a multi-location app for the Open Graph protocol</title>
      <dc:creator>Shy Ruparel</dc:creator>
      <pubDate>Wed, 23 Jun 2021 14:44:09 +0000</pubDate>
      <link>https://dev.to/contentful/how-to-build-a-multi-location-app-for-the-open-graph-protocol-2ao6</link>
      <guid>https://dev.to/contentful/how-to-build-a-multi-location-app-for-the-open-graph-protocol-2ao6</guid>
      <description>&lt;p&gt;I'm cross-posting this article for my &lt;a href="https://www.contentful.com/blog/author/david-fateh/" rel="noopener noreferrer"&gt;Colleague David Fateh&lt;/a&gt;. Check out the original post over on the &lt;a href="https://www.contentful.com/blog/2021/04/27/build-multi-location-app-open-graph-protocol/" rel="noopener noreferrer"&gt;Contentful Blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are a content creator for the web, you may have used the &lt;a href="https://ogp.me/" rel="noopener noreferrer"&gt;Open Graph protocol&lt;/a&gt;. The Open Graph protocol is a way to display and share information about your resource on the internet. You may have seen these in action when sharing websites and URLs on Twitter and Facebook, or when you are chatting with your coworkers on Slack. The preview and metadata for the link is shown to the user as in the Twitter post below.&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%2Fkx2oe9yfylr3nfp3whvg.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%2Fkx2oe9yfylr3nfp3whvg.png" alt="Screenshot of Harald Wartig's tweet" width="799" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Building an app on the &lt;a href="https://www.contentful.com/developers/docs/extensibility/app-framework/" rel="noopener noreferrer"&gt;App Framework&lt;/a&gt; can be as simple as creating a single custom button or as complicated as composing multiple components in different locations of Contentful. I want to run through an example app I've created which makes use of the App Framework's ability to handle complexity, but more importantly shows how to pass data between different &lt;a href="https://www.contentful.com/developers/docs/extensibility/app-framework/locations/" rel="noopener noreferrer"&gt;app locations&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In Contentful, we can create our own Open Graph data by making use of a simple content type that provides the user with the necessary fields to show these kinds of previews. Then, using the App Framework, we can augment the editor experience to allow for automated title generation of our content as well as a visual preview.&lt;/p&gt;

&lt;p&gt;In this post, we will cover a two things: 1. Creating a content type to handle simple Open Graph data. 2. Creating an app which helps visualize the data and allows for a preview mode. 3. When all is said and done, our Open Graph content type will still need to be pulled from a Contentful API response when we generate our web pages. The information contained in an Open Graph entry will be enough information to populate meta tags in the head of our HTML filles. Let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the content type
&lt;/h2&gt;

&lt;p&gt;We can start by creating a new content type in our space called "Open Graph," which will have five fields:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Title: short text&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Content: single reference (in our case, referencing blog posts)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Type: short text&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Image: media&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;URL: short text&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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%2F537s89b40y39q6bcfwfz.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%2F537s89b40y39q6bcfwfz.png" alt="Screenshot of Contentful content type" width="799" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This content type does a few things. First, it allows editors to pick a reference entry. In many cases, this could be a blog post, web page, link or other type of media. In our example, we will be using the Open Graph content type in a blog. This means our Content reference field will reference "Blog Post" content types.&lt;/p&gt;

&lt;p&gt;Second, we can automate the "Title" field by asking the editor to select a blog post and grabbing the title from that post. This is something we will look at when we dive into the code.&lt;/p&gt;

&lt;p&gt;Lastly, we can create a custom button that will show the editor a preview of our Open Graph data as it might appear in a card UI such as Twitter or Facebook. &lt;/p&gt;

&lt;p&gt;Now that we have our content type set up, let's start developing our app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the app
&lt;/h2&gt;

&lt;p&gt;For those that are familiar with the App Framework and want to see the source code of the app, you can see it &lt;a href="https://github.com/davidfateh/ctfl-open-graph" rel="noopener noreferrer"&gt;here on GitHub&lt;/a&gt;. For those less familiar, I've written a few tutorials in the past about how to build apps. We also have a tutorial for beginners, &lt;a href="https://www.contentful.com/developers/docs/extensibility/app-framework/tutorial/" rel="noopener noreferrer"&gt;Building your first app&lt;/a&gt;, where we explain all the necessary steps to get up and running.&lt;/p&gt;

&lt;p&gt;An app is a single page application that runs inside of an iframe and can be rendered in different locations inside of the Contentful Web App. While building an app can be done using any framework (or lack thereof), I'm going to use the &lt;a href="https://www.contentful.com/developers/docs/extensibility/app-framework/create-contentful-app/" rel="noopener noreferrer"&gt;Create Contentful App&lt;/a&gt; CLI tool, which will bootstrap a React app with all the necessary logic required to get up and running inside of Contentful.&lt;/p&gt;

&lt;p&gt;To initialize and run our app locally, run these commands in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @contentful/create-contentful-app init open-graph
&lt;span class="nb"&gt;cd &lt;/span&gt;open-graph
npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app is now running as a single page application on &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;&lt;code&gt;http://localhost:3000&lt;/code&gt;&lt;/a&gt;. In order to see our app in Contentful, we will need to create an &lt;a href="https://www.contentful.com/developers/docs/extensibility/app-framework/app-definition/" rel="noopener noreferrer"&gt;AppDefinition&lt;/a&gt; and assign our app to show up in the correct locations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the AppDefinition
&lt;/h2&gt;

&lt;p&gt;An AppDefinition is the entity that represents an app in Contentful. It contains general app information, like where it is visible. It also provides settings that enable it to &lt;a href="https://www.contentful.com/developers/docs/extensibility/app-framework/app-identity/" rel="noopener noreferrer"&gt;run independently&lt;/a&gt;, or enable particular settings for any current and future installations.&lt;/p&gt;

&lt;p&gt;Note: In order to create an AppDefinition, you must be a Contentful organization admin or developer. If you are not, you can ask your admin for access or sign up for a free Contentful account to use as a sandbox.&lt;/p&gt;

&lt;p&gt;Let's create a new AppDefinition. Go to your organization settings. In the top menu, select Apps. From here we will create a new AppDefinition with the following properties:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Name: Open Graph&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;App URL: &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; (remember this is running on our machine)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;First location: Field - short text &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Second location: Page&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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%2Fkp1827epwtosmswpsnmc.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%2Fkp1827epwtosmswpsnmc.png" alt="Screenshot of the new AppDefinition field in Contentful" width="800" height="799"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once filled in, hit the Create button in the top-right corner. Next, let's install our app into the space where we created our Open Graph content type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing and developing the app
&lt;/h2&gt;

&lt;p&gt;To install the app, head over to the space where we created our Open Graph content type. In the Apps menu bar, click "Manage apps" then find the Open Graph app in the list of available apps (it will show up with a private tag next to it). Click Install.&lt;/p&gt;

&lt;p&gt;Once our app is installed, it is ready to be assigned to our Open Graph content type. Navigate to the Content Model page and select the Open Graph content type. In there we will modify the settings of our "Title" field by navigating to the appearance area selecting our Open Graph app. Click save.&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%2Fa4yde17ahi97hzhn6kyz.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%2Fa4yde17ahi97hzhn6kyz.png" alt="Illustration of the Open Graph content type in Contentful" width="800" height="593"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, create a new Open Graph entry to see how the app renders. We can see that our app is rendering by the message that is showing up in our Title field. Of course, we'd like it if our title field actually showed a title instead of a developer message, so at this point it is time to dive into the code that was created earlier by our create-contentful-app CLI tool.&lt;/p&gt;

&lt;p&gt;To start, create a field that shows a title based off of the reference blog post in our Content field. To get this functionality, we will make use of &lt;a href="https://f36.contentful.com/" rel="noopener noreferrer"&gt;Forma 36 --- the Contentful Design System&lt;/a&gt; to implement UI elements which have the same look and feel as Contentful itself. We're going to use a TextInput which will be disabled for manual editing but will automatically pull the title information from our content reference field.&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%2Ffzbv2yvv9kv319a0yd8z.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%2Ffzbv2yvv9kv319a0yd8z.png" alt="Screenshot of the process of creating an Open Graph entry" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our code, modify src/components/Field.tsx to create a better experience.&lt;/p&gt;

&lt;p&gt;To start, import a few libraries that come out of the box when we ran the create-contentful-app CLI commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&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;react&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="nx"&gt;tokens&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;@contentful/forma-36-tokens&lt;/span&gt;&lt;span class="dl"&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;TextInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Button&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;@contentful/forma-36-react-components&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;FieldExtensionSDK&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;@contentful/app-sdk&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;We are using React here for our UI. Tokens is a Forma 36 utility that will provide us with certain variables like color and sizing. The Forma 36 components also come out of the box and will be useful for constructing our UI. Since we are using TypeScript, I'm also including the SDK typings by default.&lt;/p&gt;

&lt;p&gt;Next, we'll want to modify the component itself to handle state, pull information from another field and render some UI.&lt;/p&gt;

&lt;p&gt;First, create some state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FieldProps&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="c1"&gt;// Get the title of the field and create a setter&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;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTitle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, listen for changes to our &lt;code&gt;content&lt;/code&gt; reference field. When the &lt;code&gt;content&lt;/code&gt; field has been populated with data, grab the entry, find the title and set that as the title in state. Also save that as the title field's value in the Open Graph content type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nf"&gt;useEffect&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="c1"&gt;// Resize the field so it isn't cut off in the UI&lt;/span&gt;
    &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startAutoResizer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// when the value of the `content` field changes, set the title&lt;/span&gt;
    &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onValueChanged&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;entry&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setTitle&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="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// The content field is a reference to a blog post.&lt;/span&gt;
      &lt;span class="c1"&gt;// We want to grab that full entry to get the `title` of it&lt;/span&gt;
      &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getEntry&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BlogPost&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;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sys&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="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&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;title&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;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
          &lt;span class="nf"&gt;setTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Last, we can render our UI based on the above logic to give editors some validation hints. We can either warn that a reference needs to be picked, or if a reference is picked, to use the correct title information in our field. We are also going to include a button, which will open up our page location by passing the entry's ID. We will touch on that more below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt; &lt;span class="nx"&gt;noteType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;blog&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="nx"&gt;below&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;assign&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Note&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TextInput&lt;/span&gt; &lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="na"&gt;marginBottom&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;spacingM&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&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="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openCurrentAppPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSys&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;})}&lt;/span&gt; &lt;span class="nx"&gt;buttonType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;naked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Preview&lt;/span&gt; &lt;span class="nx"&gt;Open&lt;/span&gt; &lt;span class="nx"&gt;Graph&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;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;Our full component code looks like this now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&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;react&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="nx"&gt;tokens&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;@contentful/forma-36-tokens&lt;/span&gt;&lt;span class="dl"&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;TextInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Button&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;@contentful/forma-36-react-components&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;FieldExtensionSDK&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;@contentful/app-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;FieldProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FieldExtensionSDK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// Custom type to denote a blog post content type&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;BlogPost&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;sys&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;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nl"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;title&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;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FieldProps&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="c1"&gt;// Get the title of the field and create a setter&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;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTitle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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="c1"&gt;// Resize the field so it isn't cut off in the UI&lt;/span&gt;
    &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startAutoResizer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// when the value of the `content` field changes, set the title&lt;/span&gt;
    &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onValueChanged&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;entry&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setTitle&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="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// The content field is a reference to a blog post.&lt;/span&gt;
      &lt;span class="c1"&gt;// We want to grab that full entry to get the `title` of it&lt;/span&gt;
      &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getEntry&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BlogPost&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;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sys&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="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&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;title&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;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
          &lt;span class="nf"&gt;setTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&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="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;title&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt; &lt;span class="nx"&gt;noteType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;blog&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="nx"&gt;below&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;assign&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Note&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TextInput&lt;/span&gt; &lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="na"&gt;marginBottom&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;spacingM&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&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="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openCurrentAppPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSys&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;})}&lt;/span&gt; &lt;span class="nx"&gt;buttonType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;naked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Preview&lt;/span&gt; &lt;span class="nx"&gt;Open&lt;/span&gt; &lt;span class="nx"&gt;Graph&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have a field that can automatically pull a title from the referenced blog post, we also want to build out a page location, which will show a preview of the Open Graph to editors when they click the preview button. The button is under our Title and says "Preview Open Graph." This button will direct us to our page location which we will create soon.&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%2Fwfsijjr5tmfa1hpmrp85.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%2Fwfsijjr5tmfa1hpmrp85.png" alt="Screenshot of Open Graph title preview function" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's start by modifying our src/components/Page.tsx file. Again, we are going to import a few things from Forma 36 in order to build a nice preview UI for our Open Graph components.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&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;react&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;Card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Heading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Paragraph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Typography&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;TextLink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;SkeletonBodyText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;SkeletonContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;SkeletonDisplayText&lt;/span&gt;&lt;span class="p"&gt;,&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;@contentful/forma-36-react-components&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="nx"&gt;tokens&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;@contentful/forma-36-tokens&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;PageExtensionSDK&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;@contentful/app-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;In&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt; &lt;span class="nx"&gt;parameters&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;we&lt;/span&gt; &lt;span class="nx"&gt;passed&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;when&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt; &lt;span class="nx"&gt;clicked&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;button&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;Page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PageProps&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="c1"&gt;// false indicates an error occurred&lt;/span&gt;
    &lt;span class="c1"&gt;// otherwise the entry is loading or loaded&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;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setEntry&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;OpenGraphPreview&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;useEffect&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="c1"&gt;// Get the entry ID which is passed in via the URL&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entryId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&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;invocation&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&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="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// If no entry ID exists, show an error message by setting content to false&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;entryId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;setEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&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 use &lt;code&gt;sdk.parameters.invocation.path&lt;/code&gt; to access the custom path in the URL of our app, which was passed in by &lt;code&gt;sdk.navigator.openCurrentAppPage&lt;/code&gt; from our field component as the on click function of the button.&lt;/p&gt;

&lt;p&gt;Now, complete the &lt;code&gt;useEffect&lt;/code&gt; function with some network calls to get all the data we need to display the preview:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;        &lt;span class="c1"&gt;// Get the entry data by getting the linked asset and content body&lt;/span&gt;
        &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getEntry&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;OpenGraphEntry&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;entryId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="c1"&gt;// Grabs the Image asset of the Open Graph content type&lt;/span&gt;
                &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAsset&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Asset&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;sys&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="c1"&gt;// Grabs the long text body of the blog post&lt;/span&gt;
                &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getEntry&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Content&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;sys&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="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&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="c1"&gt;// combine the data from the two `space` calls&lt;/span&gt;
                &lt;span class="nf"&gt;setEntry&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="na"&gt;imageUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;previewBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&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;entryId&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="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;Notice that we're using &lt;code&gt;Promise.all&lt;/code&gt; to get data about the entry and the image asset which we will use in our preview.&lt;/p&gt;

&lt;p&gt;Now render the component UI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt; &lt;span class="nx"&gt;noteType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;negative&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="nx"&gt;retrieving&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;!&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Note&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;
            &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
                &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100vh&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;alignItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;marginTop&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;spacingXl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;flexDirection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;column&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="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;justifyContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SkeletonContainer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SkeletonDisplayText&lt;/span&gt; &lt;span class="nx"&gt;numberOfLines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SkeletonBodyText&lt;/span&gt; &lt;span class="nx"&gt;numberOfLines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;offsetTop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/SkeletonContainer&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Typography&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Heading&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nx"&gt;Open&lt;/span&gt; &lt;span class="nx"&gt;Graph&lt;/span&gt; &lt;span class="nx"&gt;Preview&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Heading&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Typography&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Card&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;260px&lt;/span&gt;&lt;span class="dl"&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SkeletonContainer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SkeletonDisplayText&lt;/span&gt; &lt;span class="nx"&gt;numberOfLines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SkeletonBodyText&lt;/span&gt; &lt;span class="nx"&gt;numberOfLines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;offsetTop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/SkeletonContainer&amp;gt; &lt;/span&gt;&lt;span class="err"&gt;:
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Typography&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TextLink&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&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;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/TextLink&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Paragraph&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;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previewBody&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Paragraph&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Typography&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;imageUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;200px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;}
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Card&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt;
                &lt;span class="nx"&gt;buttonType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;muted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                &lt;span class="nx"&gt;disabled&lt;/span&gt;&lt;span class="o"&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;entry&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&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="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;!&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="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spacingXl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; 0`&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;Back&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;justifyContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SkeletonContainer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SkeletonDisplayText&lt;/span&gt; &lt;span class="nx"&gt;numberOfLines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SkeletonBodyText&lt;/span&gt; &lt;span class="nx"&gt;numberOfLines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;offsetTop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/SkeletonContainer&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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;For the most part, I am using Skeleton components, which are for looks; they show a loading preview. The Card component is used for actually displaying the content in the Open Graph card. All together, the Page component code looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&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;react&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;Card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Heading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Paragraph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Typography&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;TextLink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;SkeletonBodyText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;SkeletonContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;SkeletonDisplayText&lt;/span&gt;&lt;span class="p"&gt;,&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;@contentful/forma-36-react-components&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="nx"&gt;tokens&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;@contentful/forma-36-tokens&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;PageExtensionSDK&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;@contentful/app-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PageProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PageExtensionSDK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// An open graph content type&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;OpenGraphEntry&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;title&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;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="nl"&gt;type&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;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="nl"&gt;url&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;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="nl"&gt;image&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;en-US&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="na"&gt;sys&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;string&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="nl"&gt;content&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;en-US&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="na"&gt;sys&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;string&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="p"&gt;};&lt;/span&gt;
    &lt;span class="nl"&gt;sys&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;string&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;// A custom shape for the entry we are going to render in our UI&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;OpenGraphPreview&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;imageUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;previewBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;body&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;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Asset&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;file&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;en-US&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="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&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="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PageProps&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="c1"&gt;// false indicates an error occurred&lt;/span&gt;
    &lt;span class="c1"&gt;// otherwise the entry is loading or loaded&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;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setEntry&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;OpenGraphPreview&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;useEffect&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="c1"&gt;// Get the entry ID which is passed in via the URL&lt;/span&gt;
        &lt;span class="c1"&gt;// @ts-ignore&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entryId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&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;invocation&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&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="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// If no entry ID exists, show an error message by setting content to false&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;entryId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;setEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Get the entry data by getting the linked asset and content body&lt;/span&gt;
        &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getEntry&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;OpenGraphEntry&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;entryId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="c1"&gt;// Grabs the Image asset of the Open Graph content type&lt;/span&gt;
                &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAsset&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Asset&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;sys&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="c1"&gt;// Grabs the long text body of the blog post&lt;/span&gt;
                &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getEntry&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Content&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;sys&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="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&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="c1"&gt;// combine the data from the two `space` calls&lt;/span&gt;
                &lt;span class="nf"&gt;setEntry&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="na"&gt;imageUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;previewBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&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;entryId&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="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;entry&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt; &lt;span class="nx"&gt;noteType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;negative&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="nx"&gt;retrieving&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;!&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Note&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;
            &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
                &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100vh&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;alignItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;marginTop&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;spacingXl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;flexDirection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;column&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="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;justifyContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SkeletonContainer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SkeletonDisplayText&lt;/span&gt; &lt;span class="nx"&gt;numberOfLines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SkeletonBodyText&lt;/span&gt; &lt;span class="nx"&gt;numberOfLines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;offsetTop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/SkeletonContainer&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Typography&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Heading&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nx"&gt;Open&lt;/span&gt; &lt;span class="nx"&gt;Graph&lt;/span&gt; &lt;span class="nx"&gt;Preview&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Heading&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Typography&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Card&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;260px&lt;/span&gt;&lt;span class="dl"&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SkeletonContainer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SkeletonDisplayText&lt;/span&gt; &lt;span class="nx"&gt;numberOfLines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SkeletonBodyText&lt;/span&gt; &lt;span class="nx"&gt;numberOfLines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;offsetTop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/SkeletonContainer&amp;gt; &lt;/span&gt;&lt;span class="err"&gt;:
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Typography&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TextLink&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&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;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/TextLink&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Paragraph&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;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previewBody&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Paragraph&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Typography&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;imageUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;200px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;}
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Card&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt;
                &lt;span class="nx"&gt;buttonType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;muted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                &lt;span class="nx"&gt;disabled&lt;/span&gt;&lt;span class="o"&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;entry&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&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="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;!&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="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spacingXl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; 0`&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;Back&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;justifyContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SkeletonContainer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SkeletonDisplayText&lt;/span&gt; &lt;span class="nx"&gt;numberOfLines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SkeletonBodyText&lt;/span&gt; &lt;span class="nx"&gt;numberOfLines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;offsetTop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/SkeletonContainer&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the final code, I've added some &lt;a href="https://github.com/davidfateh/ctfl-open-graph/blob/main/src/components/Page.tsx#L22" rel="noopener noreferrer"&gt;Typescript interfaces&lt;/a&gt; to ensure we are working with the correct data structures. If you'd like to check out the complete code, visit the repo: &lt;a href="https://github.com/davidfateh/ctfl-open-graph" rel="noopener noreferrer"&gt;davidfateh/ctfl-open-graph: Multi location app for Open Graph&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you'd like to go beyond this functionality, Salma recently &lt;a href="https://www.contentful.com/blog/2021/03/17/puppeteer-node-open-graph-screenshot-for-socials/" rel="noopener noreferrer"&gt;wrote a post&lt;/a&gt; on solving this issue using Puppeteer and Node.js to generate this data programmatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Passing data between app locations can be very useful for creating cohesive experiences for editors as they work on content that is meant to be visualized in different ways. The Open Graph app is one example of how retrieving entry data and passing it around inside of an app can extend some basic functionality.&lt;/p&gt;

&lt;p&gt;If you have an app idea or are working on a prototype, our &lt;a href="https://www.contentful.com/slack/" rel="noopener noreferrer"&gt;Slack Community&lt;/a&gt; can be very helpful if you want to share or get help implementing ideas. You can take advantage of some of the cool things we are doing and discussing over there. Our extensibility team and I are active in our community, and we are always curious to hear ideas about your next big projects!&lt;/p&gt;

</description>
      <category>opengraph</category>
      <category>contentful</category>
      <category>webdev</category>
    </item>
    <item>
      <title>App Framework tutorial: Building a custom reference app</title>
      <dc:creator>Shy Ruparel</dc:creator>
      <pubDate>Wed, 23 Jun 2021 14:40:52 +0000</pubDate>
      <link>https://dev.to/contentful/app-framework-tutorial-building-a-custom-reference-app-1bbc</link>
      <guid>https://dev.to/contentful/app-framework-tutorial-building-a-custom-reference-app-1bbc</guid>
      <description>&lt;p&gt;I'm cross-posting this article for my &lt;a href="https://www.contentful.com/blog/author/david-fateh/" rel="noopener noreferrer"&gt;Colleague David Fateh&lt;/a&gt;. Check out the original post over on the &lt;a href="https://www.contentful.com/blog/2021/04/22/app-framework-tutorial-custom-reference-app/" rel="noopener noreferrer"&gt;Contentful Blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.contentful.com/blog/author/shy-ruparel/" rel="noopener noreferrer"&gt;Shy&lt;/a&gt; and I set out on a &lt;a href="https://youtu.be/UFMNO5ZXce0" rel="noopener noreferrer"&gt;livestream&lt;/a&gt; to customize the Contentful reference field by using the &lt;a href="https://www.contentful.com/developers/docs/extensibility/app-framework/" rel="noopener noreferrer"&gt;App Framework&lt;/a&gt; to build a custom React app that would provide us with some new and novel functionality. While our progress during the live stream was good, I took the liberty of tightening up the styling and making sure additional functionality was possible. In this post, I'm going to show you how I created a custom reference field app which provides custom functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building on the App Framework
&lt;/h2&gt;

&lt;p&gt;Building an app on the App Framework as a developer requires an understanding of how editors --- the people who use the Contentful web app --- may want to manipulate and view entry data. We know that everyone has different use cases and so the customization allowed by the App Framework can be useful when trying to create an experience that will help solve these unique cases. &lt;/p&gt;

&lt;p&gt;When you define fields in an entry, you are defining how data should be displayed. In the case of the reference field, data such as the title of the entry is displayed by default like in the screenshot below:&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%2Fdx4zdsymfdrp1a923ze3.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%2Fdx4zdsymfdrp1a923ze3.png" alt="Screenshot of what defining fields in an entry in Contentful looks like" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can enhance this experience by creating a visually similar custom app using the App Framework that can show more data from the referenced entry such as the body of the post, not just the title.&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%2F8udbs4u4fk48aifaqmqj.gif" 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%2F8udbs4u4fk48aifaqmqj.gif" alt="Gif showing an app that can show more data from the referenced entry." width="600" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;To start, we used the &lt;a href="https://www.contentful.com/developers/docs/extensibility/app-framework/create-contentful-app/" rel="noopener noreferrer"&gt;create-contentful-app&lt;/a&gt; CLI tool. The tool is available for free to developers, and they can use it to quickly get an app up and running. The create-contentful-app CLI tool creates a React app project with &lt;a href="https://f36.contentful.com/" rel="noopener noreferrer"&gt;Forma 36&lt;/a&gt; (our design library) and our &lt;a href="https://www.contentful.com/developers/docs/extensibility/app-framework/field-editors/" rel="noopener noreferrer"&gt;open source field editors&lt;/a&gt; for easy access.&lt;/p&gt;

&lt;p&gt;We get all this by running the commands below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @contentful/create-contentful-app init reference-field-app
&lt;span class="nb"&gt;cd &lt;/span&gt;reference-field-app
npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point our app is running on &lt;code&gt;localhost:3000&lt;/code&gt; but won't be accessible until we create the &lt;a href="https://www.contentful.com/developers/docs/extensibility/app-framework/app-definition/" rel="noopener noreferrer"&gt;AppDefinition&lt;/a&gt; and select the locations where we want it to show up. Let's do this next.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the AppDefinition
&lt;/h3&gt;

&lt;p&gt;An &lt;a href="https://www.contentful.com/developers/docs/extensibility/app-framework/app-definition/" rel="noopener noreferrer"&gt;AppDefinition&lt;/a&gt; is the entity that represents an app in Contentful. You can think of it as a sort of blueprint for how your app will interact inside the Contentful experience.&lt;/p&gt;

&lt;p&gt;You must have an admin or developer account in your Contentful organization. Many developers find it easiest to create a free Contentful organization for a developer environment. You can also develop an app in your primary organization if you prefer to develop in a space or environment which isn't production facing.&lt;/p&gt;

&lt;p&gt;To create the AppDefinition, head to your organization settings and click on Apps in the top menu bar. Once on the AppDefinition page, click the button to create a new app.&lt;/p&gt;

&lt;p&gt;First, we are going to name our app: Custom Reference Field. Next, we will make its app URL &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;. This is where our local app is currently running. Last, we are going to select the field location and pick the reference field (many) and click the confirm button to save this AppDefinition.&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%2F3bzps8cp1exz9n79ltad.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%2F3bzps8cp1exz9n79ltad.png" alt="Screenshot showing how the process of creating the App Definition works" width="799" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacing a built-in reference field with our custom app
&lt;/h3&gt;

&lt;p&gt;Let's now head over to a space where we want to see our app show up. In the top menu of our space or environment, click Apps then Manage apps. From here we can find our newly created Custom Reference Field app and install it into our space.&lt;/p&gt;

&lt;p&gt;Since we are building an app that will take over a reference field, we must also have a content type that makes use of a reference field. For our purposes, we are going to use a blog post list content type as an example.&lt;/p&gt;

&lt;p&gt;In our content model, we are going to adjust the field of our blog post list to use our app by editing the settings of the reference field and selecting appearance. Let's choose our newly created Custom Reference Field app.&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%2Furlockovgx9tnpd6lu5s.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%2Furlockovgx9tnpd6lu5s.png" alt="Screenshot showing how to replace a built-in reference app with a custom app" width="800" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Coding the app for custom functionality
&lt;/h3&gt;

&lt;p&gt;Once the app is assigned to a field, we can head over to our entries section and find an entry to see how the app is displayed. In our example, we have a reference field which links to different blog posts. Each blog post has a rich text field which we'd like to use to display some more custom information --- something that will give us a bit more functionality than the default reference field experience.&lt;/p&gt;

&lt;p&gt;Let's create some custom functionality. First, we are going to replace our Hello World component with components from Forma 36, as well as utilities from React and a Contentful &lt;a href="https://www.npmjs.com/package/@contentful/rich-text-react-renderer" rel="noopener noreferrer"&gt;rich text renderer&lt;/a&gt; for display use. We are also going to use the handy &lt;a href="https://contentful-field-editors.netlify.app/reference-multiple" rel="noopener noreferrer"&gt;MultipleEntryReferenceEditor&lt;/a&gt; which is part of our &lt;a href="https://contentful-field-editors.netlify.app/" rel="noopener noreferrer"&gt;open source editor package&lt;/a&gt;. We are using the rich text renderer in this situation because our referenced content model of blog posts uses a rich text field for its body. Depending on the content type, you are referencing in your custom reference field app, displaying this data through different custom or open source components is possible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&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;react&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;Card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Typography&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Heading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;CardActions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;DropdownList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;DropdownListItem&lt;/span&gt;&lt;span class="p"&gt;,&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;@contentful/forma-36-react-components&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;MultipleEntryReferenceEditor&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;@contentful/field-editor-reference&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;documentToReactComponents&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;@contentful/rich-text-react-renderer&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;FieldExtensionSDK&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;@contentful/app-sdk&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;Next, let's replace the Field component with custom code using the MultipleEntryReferenceEditor. You'll notice that while this markup is not very complicated, we will have to implement a &lt;code&gt;customRenderer&lt;/code&gt; for our MultipleEntryReferenceEditor to show properly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FieldProps&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="nf"&gt;useEffect&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;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startAutoResizer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MultipleEntryReferenceEditor&lt;/span&gt;
            &lt;span class="nx"&gt;renderCustomCard&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;customRenderer&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;viewType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;isInitiallyDisabled&lt;/span&gt;
            &lt;span class="nx"&gt;hasCardEditActions&lt;/span&gt;
            &lt;span class="nx"&gt;parameters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
                &lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;showCreateEntityAction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;showLinkEntityAction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;}}&lt;/span&gt;
        &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's create the custom renderer function which will render some more React components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customRenderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&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="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;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blogPost&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="kc"&gt;false&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;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localeCode&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Untitled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Card&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;flexGrow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flex&lt;/span&gt;&lt;span class="dl"&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&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;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cardDragHandle&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;flexGrow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1em&lt;/span&gt;&lt;span class="dl"&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Typography&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;marginBottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;20px&lt;/span&gt;&lt;span class="dl"&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Heading&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;borderBottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1px solid gray&lt;/span&gt;&lt;span class="dl"&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;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Heading&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
                            &lt;span class="nf"&gt;documentToReactComponents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                                &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localeCode&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;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Typography&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1em&lt;/span&gt;&lt;span class="dl"&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CardActions&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DropdownList&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DropdownListItem&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onEdit&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;Edit&lt;/span&gt;
                            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/DropdownListItem&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DropdownListItem&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onRemove&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;Remove&lt;/span&gt;
                            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/DropdownListItem&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/DropdownList&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CardActions&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Card&lt;/span&gt;&lt;span class="err"&gt;&amp;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;Now let's put it all together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&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;react&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;Card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Typography&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Heading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;CardActions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;DropdownList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;DropdownListItem&lt;/span&gt;&lt;span class="p"&gt;,&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;@contentful/forma-36-react-components&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;MultipleEntryReferenceEditor&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;@contentful/field-editor-reference&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;documentToReactComponents&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;@contentful/rich-text-react-renderer&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;FieldExtensionSDK&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;@contentful/app-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;FieldProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FieldExtensionSDK&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;customRenderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&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="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;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blogPost&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="kc"&gt;false&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;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localeCode&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Untitled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Card&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;flexGrow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flex&lt;/span&gt;&lt;span class="dl"&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&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;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cardDragHandle&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;flexGrow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1em&lt;/span&gt;&lt;span class="dl"&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Typography&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;marginBottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;20px&lt;/span&gt;&lt;span class="dl"&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Heading&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;borderBottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1px solid gray&lt;/span&gt;&lt;span class="dl"&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;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Heading&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
                            &lt;span class="nf"&gt;documentToReactComponents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                                &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localeCode&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;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Typography&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1em&lt;/span&gt;&lt;span class="dl"&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CardActions&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DropdownList&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DropdownListItem&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onEdit&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;Edit&lt;/span&gt;
                            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/DropdownListItem&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DropdownListItem&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onRemove&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;Remove&lt;/span&gt;
                            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/DropdownListItem&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/DropdownList&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CardActions&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Card&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&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;Field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FieldProps&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="nf"&gt;useEffect&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;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startAutoResizer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MultipleEntryReferenceEditor&lt;/span&gt;
            &lt;span class="nx"&gt;renderCustomCard&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;customRenderer&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;viewType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;isInitiallyDisabled&lt;/span&gt;
            &lt;span class="nx"&gt;hasCardEditActions&lt;/span&gt;
            &lt;span class="nx"&gt;parameters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
                &lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;showCreateEntityAction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;showLinkEntityAction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;}}&lt;/span&gt;
        &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now have a working component that not only shows the title of the blog post but also the first line of the blog post. We have successfully transformed the default experience of the reference field to show data that is more conducive for our use-case. If you're interested in seeing the full code, check out the repo &lt;a href="https://github.com/davidfateh/reference-field-app" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrapping up
&lt;/h3&gt;

&lt;p&gt;For our purposes, changing the fields inside of an entry can be a very powerful tool. There are many cases for why you may want to either slightly modify existing functionality or totally recreate your own. For the cases where you'd like to simply modify the web app, we provide the default fields as a React component in our open source editor library. For cases where you'd like to create your own experience, Forma 36 can be an invaluable tool for achieving a very fluid look and feel of your UI without having to spend time messing with layout yourself.&lt;/p&gt;

&lt;p&gt;That said, it is always interesting to see the different ways developers have come together and built their own components for the UI/UX they envision for their users. Many developers make use of our &lt;a href="https://www.contentful.com/slack/" rel="noopener noreferrer"&gt;Slack Community&lt;/a&gt; where help and ideas are easily shared. I'm active on the channel and am always happy to help explore ideas or guide other developers through the app creation process. &lt;/p&gt;

&lt;p&gt;If you'd like to join our community, you can take advantage of some of the cool things we are doing and discussing over there. If you are interested in seeing more video tutorials, check out our weekly streams on &lt;a href="https://www.twitch.tv/contentfuldevs" rel="noopener noreferrer"&gt;Twitch&lt;/a&gt; and &lt;a href="https://www.youtube.com/channel/UCrsEcxPCJlz5yZ_FKGo6D9g" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt; where we code live and work through problems on the fly!&lt;/p&gt;

&lt;p&gt;If you haven't signed up for a free Contentful account yet, register for a Community edition!&lt;/p&gt;

</description>
      <category>contentful</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Visualize your Content Models with ContentModel.io</title>
      <dc:creator>Shy Ruparel</dc:creator>
      <pubDate>Wed, 26 May 2021 14:15:37 +0000</pubDate>
      <link>https://dev.to/contentful/visualize-your-content-models-with-contentmodel-io-1nlf</link>
      <guid>https://dev.to/contentful/visualize-your-content-models-with-contentmodel-io-1nlf</guid>
      <description>&lt;p&gt;Developer Advocate &lt;a href="https://twitter.com/shyruparel" rel="noopener noreferrer"&gt;Shy Ruparel&lt;/a&gt; takes a look at ContentModel.io, a tool to visualize Content Models. ContentModel.io also let's you share your own content models and import the models that others have made.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to post to Twitter using Contentful webhooks and IFTTT</title>
      <dc:creator>Shy Ruparel</dc:creator>
      <pubDate>Mon, 24 May 2021 14:30:03 +0000</pubDate>
      <link>https://dev.to/contentful/how-to-post-to-twitter-using-contentful-webhooks-and-ifttt-4eb9</link>
      <guid>https://dev.to/contentful/how-to-post-to-twitter-using-contentful-webhooks-and-ifttt-4eb9</guid>
      <description>&lt;p&gt;Want to post to Twitter every time you publish a blog post? Don't have the time to build a Twitter API integration? Contentful Developer Evangelist &lt;a href="https://twitter.com/ShyRuparel/" rel="noopener noreferrer"&gt;Shy Ruparel&lt;/a&gt; shows us how easy it is to set up social media posting using Contentful webhooks and IFTTT (If This Then That).&lt;/p&gt;

</description>
      <category>webhook</category>
      <category>twitter</category>
      <category>contentful</category>
      <category>ifttt</category>
    </item>
    <item>
      <title>Contentful Updates: Introducing new APIs for managing bulk content</title>
      <dc:creator>Shy Ruparel</dc:creator>
      <pubDate>Wed, 12 May 2021 14:59:25 +0000</pubDate>
      <link>https://dev.to/contentful/contentful-updates-introducing-new-apis-for-managing-bulk-content-3ahd</link>
      <guid>https://dev.to/contentful/contentful-updates-introducing-new-apis-for-managing-bulk-content-3ahd</guid>
      <description>&lt;p&gt;Every month Shy from the Contentful Dev Rel Team will share his favorite changes from the &lt;a href="https://www.contentful.com/developers/changelog/" rel="noopener noreferrer"&gt;Contentful Changelog&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;This month is so jam packed he's made two videos. For this one he's sharing all of the details about the three newest &lt;a href="https://www.contentful.com/developers/changelog/#introducing-new-apis-for-managing-bulk-content" rel="noopener noreferrer"&gt;Contentful APIs for managing content in Bulk Content&lt;/a&gt; &lt;/p&gt;

</description>
    </item>
    <item>
      <title>How does GraphiQL work? An intro to Introspection.</title>
      <dc:creator>Shy Ruparel</dc:creator>
      <pubDate>Wed, 12 May 2021 14:36:02 +0000</pubDate>
      <link>https://dev.to/contentful/how-does-graphiql-work-an-intro-to-introspection-4kle</link>
      <guid>https://dev.to/contentful/how-does-graphiql-work-an-intro-to-introspection-4kle</guid>
      <description>&lt;p&gt;&lt;a href="https://twitter.com/ShyRuparel/" rel="noopener noreferrer"&gt;Contentful Developer Evangelist Shy Ruparel&lt;/a&gt; took a deep dive into figuring out how does GraphiQL build the self documentation that we're all come to love as a major feature of the tool. &lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
