[{"data":1,"prerenderedAt":717},["ShallowReactive",2],{"/en-us/blog/journey-in-native-unicode-emoji/":3,"navigation-en-us":34,"banner-en-us":463,"footer-en-us":480,"Eric Eastwood":690,"next-steps-en-us":702},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"seo":8,"content":16,"config":24,"_id":27,"_type":28,"title":29,"_source":30,"_file":31,"_stem":32,"_extension":33},"/en-us/blog/journey-in-native-unicode-emoji","blog",false,"",{"title":9,"description":10,"ogTitle":9,"ogDescription":10,"noIndex":6,"ogImage":11,"ogUrl":12,"ogSiteName":13,"ogType":14,"canonicalUrls":12,"schema":15},"Our journey in switching to native Unicode emoji","Unicode is hard. Here's a guide to getting native Unicode Emoji right 👌. Learn more!","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749672573/Blog/Hero%20Images/journey-in-native-unicode-emoji-cover.png","https://about.gitlab.com/blog/journey-in-native-unicode-emoji","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Our journey in switching to native Unicode emoji\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Eric Eastwood\"}],\n        \"datePublished\": \"2018-05-30\",\n      }",{"title":9,"description":10,"authors":17,"heroImage":11,"date":19,"body":20,"category":21,"tags":22},[18],"Eric Eastwood","2018-05-30","The switch from image-based emoji to native Unicode wasn't a straightforward\njourney and included many intricacies to get production ready. Support\nvaries widely on each OS, even between the browsers on the OS. We also\nwanted to support falling back to image-based emoji for environments that do\nnot support everything yet, otherwise people would see black squares (□). As\na simple example, most Linux environments do not have Unicode emoji support\nunless you manually install a font. I consider this blog post the survival\nmanual I wish I had had when implementing native Unicode emoji myself.\n\n\n## What is Unicode emoji?\n\n\nUnicode emoji is a universal character encoding standard maintained by the\n[Unicode Consortium](https://home.unicode.org/basic-info/overview/) and It\nprovides the basis for processing, storing, and interchanging text data in\nany language. As far as emojis themselves are concerned, this is the\nencoding system that develops and houses all emojis. Emojis are encoded in\nthe Unicode system based on appearance rather than a specific semantic. \n\n\n## Are Unicode emojis compatible with all devices?\n\n\nThe short answer is yes!\n\n\nAll modern software providers have become compatible with Unicode so that\ndata can be transferred freely without corruption, regardless of platform,\nlanguage, or device. \n\n\nBefore Unicode, there were multiple character encoding systems to assign\nnumbers to each of the letters and numbers that were used by computers. But\nthese character encoding systems simply couldn’t keep up with the volume of\nlanguages using different letters and numbers. The data passing through\nthese different encodings ran the risk of being corrupted due to a lack of\nsufficient support from a given computer - particularly servers.\n\n\nAnd so, a new system was born: Unicode.\n\n\n## Why move to native Unicode emoji?\n\n\nWe decided to switch to Unicode emoji because it was in line with our\ndecision to use system fonts and it reduces the number of images loaded on a\npage. You can see the [full discussion in this\nissue](https://gitlab.com/gitlab-org/gitlab-ce/issues/26371). We were also\n[interested in](https://gitlab.com/gitlab-org/gitlab-ce/issues/22474)\n[improving](https://gitlab.com/gitlab-org/gitlab-ce/issues/27250) the award\nemoji menu (emoji reaction selector) performance, so it would open quickly\nwithout an AJAX request and with less janky scrolling.\n\n\nThe first step was to find a way to detect whether a given Unicode emoji is\nsupported. Since new emoji/characters are introduced in new\nversions/releases of Unicode specifications from the [Unicode\nConsortium](http://unicode.org/), we can consider every emoji in that\nversion supported if a single emoji in that version tests positively. There\nare exceptions to assuming support for a whole Unicode version, but we can\nhandle them individually as they come up. Unicode 10 is the current stable\nrelease but [Unicode Consortium](http://unicode.org/) is working on\nfinishing up Unicode 11 and starting on Unicode 12 at the moment. The\nUnicode Consortium has [a full table of emoji here with the representation\non various\nplatforms](https://unicode.org/emoji/charts/full-emoji-list.html).\n\n\n## Testing for native emoji Unicode support\n\n\nWe test an emoji from each Unicode version/release and cache that locally\n([`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage))\nin a support map to look up later whether a given emoji is supported. If the\nemoji isn't supported we fall back to an image or CSS sprite depending on\nthe situation.\n\n\nI couldn't find any existing library or JSON document that mapped a given\nemoji to their respective Unicode version/release, so I created my own\nproject that scrapes [emojipedia](https://emojipedia.org/) and assembles a\nJSON map,\n[`emoji-unicode-version`](https://www.npmjs.com/package/emoji-unicode-version)\non npm.\n\n\nTo test whether a Unicode emoji works, we render it to a `\u003Ccanvas>` and\ninspect the pixels in the exact middle for any color (if it is black, then\nthe test fails). We also have to ensure the emoji renders as a single\ncharacter because some emoji are made up of multiple characters (see [ZWJ\nsequences and skin tone modifier sections\nbelow](#emoji-made-up-of-multiple-characters)).\n\n\nWhen choosing a specific emoji for each version to test, be sure to choose\nsomething with color. As an example, I initially chose ⚽ `:soccer:` in the\nUnicode 5.2 range but since it is a black and white emoji, it always failed\nso I switched to ⛵ `:sailboat:`.\n\n\nWe invalidate the support map whenever your user-agent changes because emoji\nsupport changes when you get a browser or OS update. We also add a manual\n`GL_EMOJI_VERSION` for busting the cache when we update the support check\nlogic.\n\n\nYou can check out our implementation here,\n[`app/assets/javascripts/emoji/support/unicode_support_map.js`](https://gitlab.com/gitlab-org/gitlab-ce/blob/ee189fd511e1a2c06f05e0d40e1d0b8875151391/app/assets/javascripts/emoji/support/unicode_support_map.js),\n[`app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js`](https://gitlab.com/gitlab-org/gitlab-ce/blob/ee189fd511e1a2c06f05e0d40e1d0b8875151391/app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js)\n\n\n### Rendering emoji to a canvas in Internet Explorer gotchas\n\n\nWhen rendering emoji to a `\u003Ccanvas>`, IE11 didn't like our full font-stack\nand renders small black and white emoji, which are less than ideal.\n\n\nThe culprit is the `-apple-system` piece 😕\n\n\n```js\n\nctx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, \"Segoe UI\",\nRoboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif, \"Apple\nColor Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\"`;\n\n```\n\n\nBut if you simply go with the emoji part of the stack, it renders the nice\ncolorful emoji as expected,\n\n\n```js\n\nctx.font = `${fontSize}px \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI\nSymbol\"`;\n\n```\n\n\nFull font-stack | Small emoji font-stack\n\n--- | ---\n\n[![](https://about.gitlab.com/images/blogimages/journey-in-native-unicode-emoji/ie-canvas-full-font-stack.png)](/images/blogimages/journey-in-native-unicode-emoji/ie-canvas-full-font-stack-large.png)\n|\n[![](https://about.gitlab.com/images/blogimages/journey-in-native-unicode-emoji/ie-canvas-short-font-stack.png)](/images/blogimages/journey-in-native-unicode-emoji/ie-canvas-short-font-stack-large.png)\n\n\n### Unicode 1.1 emoji not rendering as colorful, fancy glyphs when using\nfull font-stack\n\n\nWe also switched to using a shorter, emoji-only font-stack in CSS to get\nsome of the Unicode 1.1 emoji to render colorfully. Read [more in the\nissue](https://gitlab.com/gitlab-org/gitlab-ce/issues/29557#note_25544684).\n\n\nFull font-stack | Small emoji font-stack\n\n--- | ---\n\n![font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto,\nOxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif, \"Apple Color\nEmoji\", \"Segoe UI Emoji\", \"Segoe UI\nSymbol\";](https://about.gitlab.com/images/blogimages/journey-in-native-unicode-emoji/unicode-1-1-full-font-stack.png)\n| ![font-family: \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI\nSymbol\";](https://about.gitlab.com/images/blogimages/journey-in-native-unicode-emoji/unicode-1-1-short-font-stack.png)\n\n\n### Render emoji on Canvas at 16px\n\n\nWe use `16px` font size when rendering to the `\u003Ccanvas>` because mobile\nSafari (iOS 9.3) will always render at 16px regardless of the font size you\nspecify.\n\n\nThe `32px` pixel example below is rendering at the same size as the `16px`\nexample. If it worked correctly, the `32px` would fill up the empty space.\n\n\n32px | 16px\n\n--- | ---\n\n\u003Ca\nhref=\"/images/blogimages/journey-in-native-unicode-emoji/16px-font-size-at-32px-large.png\">\u003Cimg\nsrcset=\"/images/blogimages/journey-in-native-unicode-emoji/16px-font-size-at-32px.png\n2x\">\u003C/a> | \u003Ca\nhref=\"/images/blogimages/journey-in-native-unicode-emoji/16px-font-size-at-16px-large.png\">\u003Cimg\nsrcset=\"/images/blogimages/journey-in-native-unicode-emoji/16px-font-size-at-16px.png\n2x\">\u003C/a>\n\n\n## Unicode emoji bounds and baseline positioning is different across\nplatforms\n\n\nAnother issue we ran into when switching is the inconsistency in how emoji\nvertically align across platforms. The baseline defined in each platform\nfont is different, which makes tweaks to center on one platform throw off\nanother. We didn't find any good solution for perfect vertical centering and\nopted just to leave it for now. You can read the [full discussion\nhere](https://gitlab.com/gitlab-org/gitlab-ce/issues/33044#note_34375144).\n\n\nFor an in-depth dive into font metrics (not emoji specific), see [*Deep dive\nCSS: font metrics, line-height and\nvertical-align*](http://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align)\nby Vincent De Oliveira (aka iamvdo).\n\n\n## Emoji fallbacks\n\n\nWe define optional fallbacks for images and CSS sprites directly on the\nelement. In terms of priority, when `data-fallback-css-class` is defined on\nthe emoji element, we opt to use the CSS sprite. We only sprite things like\nthe award emoji menu, which lists every emoji at once and potentially needs\nto fall back on everything for platforms that don't support Unicode emoji\n(like Linux).\n\n\n```html\n\n\u003Cgl-emoji data-fallback-src=\"emoji-xxx.png\"\ndata-fallback-css-class=\"emoji-xxx\">\n  xxx\n\u003C/gl-emoji>\n\n```\n\n\nWe use\n[`document.registerElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/registerElement)\nin order to hook whenever a `\u003Cgl-emoji>` is used on the page or created and\ntest whether we need to fall back. We use the deprecated v0 web components\n`document.registerElement()` over the new v1\n[`CustomElementRegistry.define()`](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define)\nbecause that only works with ES2015 class syntax and in our case, Babel is\ntranspiling everything which makes that syntax incompatible for now. It is\nalso necessary to use a [`document.registerElement()`\npolyfill](https://github.com/WebReflection/document-register-element) for\nbrowsers that don't support it like Safari.\n\n\nWhen we fall back to a CSS sprite, we add the necessary `.emoji-icon`\nclasses to the `\u003Cgl-emoji>` tag. These extra CSS classes hide the emoji\nUnicode content inside so only the background image is visible.\n\n\n```css\n\n.emoji-icon {\n  /* Hide emoji Unicode */\n  color: transparent;\n  /* Hide emoji Unicode in IE */\n  text-indent: -99em;\n  /* ... */\n}\n\n```\n\n\nYou can check out our [`\u003Cgl-emoji>` implementation\nhere](https://gitlab.com/gitlab-org/gitlab-ce/blob/ee189fd511e1a2c06f05e0d40e1d0b8875151391/app/assets/javascripts/behaviors/gl_emoji.js).\n\n\n## Emoji made up of multiple characters\n\n\nSome emoji are composed of multiple characters, which can be tricky to work\nwith in JavaScript.\n[`Array.from`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from),\n[`String.prototype.codePointAt()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt)\nare all your friends here. There is a great article, [*JavaScript has a\nUnicode problem*](https://mathiasbynens.be/notes/javascript-unicode) by\nMathias Bynens, going into more detail.\n\n\n#### Zero Width Joiner (ZWJ) sequences\n\n\nZero Width Joiner (ZWJ) sequences are composed of multiple emoji characters\njoined by a ZWJ character `\\u{200D}`, `&zwj;`(non-printing character). You\ncan read more about [ZWJ sequences\nhere](http://emojipedia.org/emoji-zwj-sequences/).\n\n\n👨‍👩‍👧‍👦 `:family_mwgb:`\n\n```\n\n[...'👨‍👩‍👧‍👦']\n\n// [\"👨\", \"‍\", \"👩\", \"‍\", \"👧\", \"‍\", \"👦\"]\n\n```\n\n\n#### Skin tone modifier\n\n\nSkin tone modifiers don't need a ZWJ character to combine with another\nemoji. You can read more about the [skin tone modifiers\nhere](http://emojipedia.org/modifiers/).\n\n\n👨🏿 `:man_tone5:`\n\n```\n\n[...'👨🏿']\n\n// [\"👨\", \"🏿\"]\n\n```\n\n\nI opted to test multiple skin tone modifier combos and only if all pass,\nconsider skin tone modifiers supported at least on a basic level. There was\nstill an outlier on macOS 10.12 where they don't have 🏇🏿\n`:horse_racing_toneX:` and I added a separate test for it.\n\n\n## Emoji discrepancies\n\n\n### Flag emoji\n\n\nOn Windows, all `:flag_xx:` emoji are rendered as two-letter international\ncharacters instead of a colorful flag like on the Apple ecosystem.\n\n\n![](https://about.gitlab.com/images/blogimages/journey-in-native-unicode-emoji/flag-emoji-windows.png)\n\n\nOn Android 6, unknown flags are rendered as two-letter international\ncharacters.\n\n\n\u003Cimg\nsrcset=\"/images/blogimages/journey-in-native-unicode-emoji/flag-emoji-android-6.png\n2x\">\n\n\nOn Android 7, unknown flags are rendered as white flags with blue question\nmarks on them.\n\n\n\u003Cimg\nsrcset=\"/images/blogimages/journey-in-native-unicode-emoji/flag-emoji-android-7.png\n2x\">\n\n\n### Keycap emoji on Windows\n\n\nKeycap (digit) emoji are a bit broken on Windows but appear to be fixed on\nChrome 57+, 3️⃣4️⃣5️⃣\n\n\nBrowser | result\n\n--- | ---\n\nChrome 55.0.2883.87 (64-bit) ❌ |\n![](https://about.gitlab.com/images/blogimages/journey-in-native-unicode-emoji/keycap-chrome-55.0.2883.87.png)\n\nChrome 56.0.2924.87 (64-bit) ❌ |\n![](https://about.gitlab.com/images/blogimages/journey-in-native-unicode-emoji/keycap-chrome-56.0.2924.87.png)\n\nChromium 57.0.2984.0 (64-bit) ✅ |\n![](https://about.gitlab.com/images/blogimages/journey-in-native-unicode-emoji/keycap-chrome-57.0.2984.0.png)\n\nChrome 58.0.2999.4 (Official Build) canary (64-bit) ✅ |\n![](https://about.gitlab.com/images/blogimages/journey-in-native-unicode-emoji/keycap-chrome-58.0.2999.4.png)\n\n\n### Skin tone splitting from base emoji when width constrained\n\n\nStarting in Chrome 60+ (maybe 59.1+), the [🤼🏿 `:wrestlers_toneX:` and 🤝🏿\n`:handshake_toneX:` emoji started splitting/breaking into separate\npieces](https://gitlab.com/gitlab-org/gitlab-ce/issues/37654) (base emoji\nand skin tone) when their container is width constrained, causing\noverflow/wrapping.\n\n\nI created a [bug report on the Chromium\ntracker](https://bugs.chromium.org/p/chromium/issues/detail?id=764859) but\nit was closed a \"WontFix\" because the `wrestlers` and `handshake` emoji are\nno longer \"classified as Emoji_Base\" in the new International Components for\nUnicode (ICU) data which is used in Chrome.\n\n\nIt's understandable that those emoji are re-classified but they should\ndisplay as two separate characters in all scenarios. The 🤼🏿\n`:wrestlers_toneX:` emoji is consistently two characters now but the 🤝🏿\n`handshake_toneX` still only splits when width constrained, which is pretty\nsketchy.\n\n\nCheck the comparison in these screenshots (or [demo for Chrome prior to\n59.1](https://codepen.io/MadLittleMods/pen/dZMeXN)),\n\n\nWindows 10 | macOS\n\n--- | ---\n\n![](https://about.gitlab.com/images/blogimages/journey-in-native-unicode-emoji/skin-tone-splitting-windows-10.png)\n| \u003Cimg\nsrcset=\"/images/blogimages/journey-in-native-unicode-emoji/skin-tone-splitting-macos.png\n2x\">\n\n\n## Colliding with the object prototype `watch` function\n\n\nI ran into a small gotcha where some code was looking in an object map for\nthe `watch` ⌚ key. In Firefox, it was pulling\n[`Object.prototype.watch()`](https://developer.mozilla.org/en-US/docs/Archive/Web/JavaScript/Object.watch)\nand causing havoc.\n\n\n```js\n\nconst emojiAliases = { foo: 'bar' };\n\n\n// Expect `undefined` but got some function\n\nemojiAliases['watch']\n\n```\n\n\nI fixed this code up by using the safe lookup\n[`Object.prototype.hasOwnProperty`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty),\n\n\n```js\n\nconst emojiAliases = { foo: 'bar' };\n\n\nObject.prototype.hasOwnProperty.call(emojiAliases, 'watch')\n\n```\n\n\n[`Object.prototype.watch()`](https://developer.mozilla.org/en-US/docs/Archive/Web/JavaScript/Object.watch)\nis now removed in Firefox 58 and the current stable release is Firefox\n59.0.2 so you probably won't run into this yourself. But it's still\nadvisable to use `Object.prototype.hasOwnProperty()` for any current/future\ncollisions.\n\n\n## Things to improve\n\n\n### Custom emoji\n\n\nWe are working on adding custom emoji (with animated GIF support). You can\ntrack [this issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/13931) and\nsee our [current iteration\nhere](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14609). It's\nnot merged yet because we need to ensure it works with [Geo\nreplication](https://docs.gitlab.com/ee/administration/geo/index.html.\n\n\n### Server-side rendered fallbacks\n\n\nTo speed up time to visible emoji (TTVE™ 😉) for people that have to fall\nback to image-based emoji, we could server-side render the fallback straight\naway.\n\n\nIn order to detect support from the server, on first page visit, we could\nset a cookie client-side (frontend JavaScript land) based on the unicode\nsupport map. Cookies are sent with each request and could be read on the\nserver.\n\n\nWe have some layers of cache on our Markdown rendering which makes this a\nbit difficult to do as we would need a response for both the `true` and\n`false` emoji support. Or we could post-process every request and update the\nrendered markdown HTML accordingly.\n\n\n### SVG fallbacks\n\n\nUsing the [EmojiOne\nSVG](https://github.com/emojione/emojione/tree/2.2.7/assets/svg) fallbacks\nwould be a nice step above the `.png` images currently. This would save on\nbandwidth and we would get nice, crisp fallback emoji.\n\n\nWe could even take it a step further and extract SVGs from the OS specific\nfonts. For older versions of Windows, we could use the Windows 10 fonts so\nthat everything has the appropriate signature black outline/stroke.\n\n\nThe EmojiOne SVGs fit nicely on macOS, so nothing to really change there.\n\n\n### Improving performance\n\n\nCurrently, we have to bundle a large `digests.json` file into our JavaScript\nbundles to get the necessary asset digest hash information to serve fallback\nimages.\n\n\nFor some quick-wins, we can remove those hashes to reduce the file size and\nserve the JSON payload async. There are some [more ideas in this\nissue](https://gitlab.com/gitlab-org/gitlab-ce/issues/39000).\n","engineering",[23],"frontend",{"slug":25,"featured":6,"template":26},"journey-in-native-unicode-emoji","BlogPost","content:en-us:blog:journey-in-native-unicode-emoji.yml","yaml","Journey In Native Unicode Emoji","content","en-us/blog/journey-in-native-unicode-emoji.yml","en-us/blog/journey-in-native-unicode-emoji","yml",{"_path":35,"_dir":36,"_draft":6,"_partial":6,"_locale":7,"data":37,"_id":459,"_type":28,"title":460,"_source":30,"_file":461,"_stem":462,"_extension":33},"/shared/en-us/main-navigation","en-us",{"logo":38,"freeTrial":43,"sales":48,"login":53,"items":58,"search":390,"minimal":421,"duo":440,"pricingDeployment":449},{"config":39},{"href":40,"dataGaName":41,"dataGaLocation":42},"/","gitlab logo","header",{"text":44,"config":45},"Get free trial",{"href":46,"dataGaName":47,"dataGaLocation":42},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":49,"config":50},"Talk to sales",{"href":51,"dataGaName":52,"dataGaLocation":42},"/sales/","sales",{"text":54,"config":55},"Sign in",{"href":56,"dataGaName":57,"dataGaLocation":42},"https://gitlab.com/users/sign_in/","sign in",[59,103,201,206,311,371],{"text":60,"config":61,"cards":63,"footer":86},"Platform",{"dataNavLevelOne":62},"platform",[64,70,78],{"title":60,"description":65,"link":66},"The most comprehensive AI-powered DevSecOps Platform",{"text":67,"config":68},"Explore our Platform",{"href":69,"dataGaName":62,"dataGaLocation":42},"/platform/",{"title":71,"description":72,"link":73},"GitLab Duo (AI)","Build software faster with AI at every stage of development",{"text":74,"config":75},"Meet GitLab Duo",{"href":76,"dataGaName":77,"dataGaLocation":42},"/gitlab-duo/","gitlab duo ai",{"title":79,"description":80,"link":81},"Why GitLab","10 reasons why Enterprises choose GitLab",{"text":82,"config":83},"Learn more",{"href":84,"dataGaName":85,"dataGaLocation":42},"/why-gitlab/","why gitlab",{"title":87,"items":88},"Get started with",[89,94,99],{"text":90,"config":91},"Platform Engineering",{"href":92,"dataGaName":93,"dataGaLocation":42},"/solutions/platform-engineering/","platform engineering",{"text":95,"config":96},"Developer Experience",{"href":97,"dataGaName":98,"dataGaLocation":42},"/developer-experience/","Developer experience",{"text":100,"config":101},"MLOps",{"href":102,"dataGaName":100,"dataGaLocation":42},"/topics/devops/the-role-of-ai-in-devops/",{"text":104,"left":105,"config":106,"link":108,"lists":112,"footer":183},"Product",true,{"dataNavLevelOne":107},"solutions",{"text":109,"config":110},"View all Solutions",{"href":111,"dataGaName":107,"dataGaLocation":42},"/solutions/",[113,138,162],{"title":114,"description":115,"link":116,"items":121},"Automation","CI/CD and automation to accelerate deployment",{"config":117},{"icon":118,"href":119,"dataGaName":120,"dataGaLocation":42},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[122,126,130,134],{"text":123,"config":124},"CI/CD",{"href":125,"dataGaLocation":42,"dataGaName":123},"/solutions/continuous-integration/",{"text":127,"config":128},"AI-Assisted Development",{"href":76,"dataGaLocation":42,"dataGaName":129},"AI assisted development",{"text":131,"config":132},"Source Code Management",{"href":133,"dataGaLocation":42,"dataGaName":131},"/solutions/source-code-management/",{"text":135,"config":136},"Automated Software Delivery",{"href":119,"dataGaLocation":42,"dataGaName":137},"Automated software delivery",{"title":139,"description":140,"link":141,"items":146},"Security","Deliver code faster without compromising security",{"config":142},{"href":143,"dataGaName":144,"dataGaLocation":42,"icon":145},"/solutions/security-compliance/","security and compliance","ShieldCheckLight",[147,152,157],{"text":148,"config":149},"Application Security Testing",{"href":150,"dataGaName":151,"dataGaLocation":42},"/solutions/application-security-testing/","Application security testing",{"text":153,"config":154},"Software Supply Chain Security",{"href":155,"dataGaLocation":42,"dataGaName":156},"/solutions/supply-chain/","Software supply chain security",{"text":158,"config":159},"Software Compliance",{"href":160,"dataGaName":161,"dataGaLocation":42},"/solutions/software-compliance/","software compliance",{"title":163,"link":164,"items":169},"Measurement",{"config":165},{"icon":166,"href":167,"dataGaName":168,"dataGaLocation":42},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[170,174,178],{"text":171,"config":172},"Visibility & Measurement",{"href":167,"dataGaLocation":42,"dataGaName":173},"Visibility and Measurement",{"text":175,"config":176},"Value Stream Management",{"href":177,"dataGaLocation":42,"dataGaName":175},"/solutions/value-stream-management/",{"text":179,"config":180},"Analytics & Insights",{"href":181,"dataGaLocation":42,"dataGaName":182},"/solutions/analytics-and-insights/","Analytics and insights",{"title":184,"items":185},"GitLab for",[186,191,196],{"text":187,"config":188},"Enterprise",{"href":189,"dataGaLocation":42,"dataGaName":190},"/enterprise/","enterprise",{"text":192,"config":193},"Small Business",{"href":194,"dataGaLocation":42,"dataGaName":195},"/small-business/","small business",{"text":197,"config":198},"Public Sector",{"href":199,"dataGaLocation":42,"dataGaName":200},"/solutions/public-sector/","public sector",{"text":202,"config":203},"Pricing",{"href":204,"dataGaName":205,"dataGaLocation":42,"dataNavLevelOne":205},"/pricing/","pricing",{"text":207,"config":208,"link":210,"lists":214,"feature":298},"Resources",{"dataNavLevelOne":209},"resources",{"text":211,"config":212},"View all resources",{"href":213,"dataGaName":209,"dataGaLocation":42},"/resources/",[215,248,270],{"title":216,"items":217},"Getting started",[218,223,228,233,238,243],{"text":219,"config":220},"Install",{"href":221,"dataGaName":222,"dataGaLocation":42},"/install/","install",{"text":224,"config":225},"Quick start guides",{"href":226,"dataGaName":227,"dataGaLocation":42},"/get-started/","quick setup checklists",{"text":229,"config":230},"Learn",{"href":231,"dataGaLocation":42,"dataGaName":232},"https://university.gitlab.com/","learn",{"text":234,"config":235},"Product documentation",{"href":236,"dataGaName":237,"dataGaLocation":42},"https://docs.gitlab.com/","product documentation",{"text":239,"config":240},"Best practice videos",{"href":241,"dataGaName":242,"dataGaLocation":42},"/getting-started-videos/","best practice videos",{"text":244,"config":245},"Integrations",{"href":246,"dataGaName":247,"dataGaLocation":42},"/integrations/","integrations",{"title":249,"items":250},"Discover",[251,256,260,265],{"text":252,"config":253},"Customer success stories",{"href":254,"dataGaName":255,"dataGaLocation":42},"/customers/","customer success stories",{"text":257,"config":258},"Blog",{"href":259,"dataGaName":5,"dataGaLocation":42},"/blog/",{"text":261,"config":262},"Remote",{"href":263,"dataGaName":264,"dataGaLocation":42},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":266,"config":267},"TeamOps",{"href":268,"dataGaName":269,"dataGaLocation":42},"/teamops/","teamops",{"title":271,"items":272},"Connect",[273,278,283,288,293],{"text":274,"config":275},"GitLab Services",{"href":276,"dataGaName":277,"dataGaLocation":42},"/services/","services",{"text":279,"config":280},"Community",{"href":281,"dataGaName":282,"dataGaLocation":42},"/community/","community",{"text":284,"config":285},"Forum",{"href":286,"dataGaName":287,"dataGaLocation":42},"https://forum.gitlab.com/","forum",{"text":289,"config":290},"Events",{"href":291,"dataGaName":292,"dataGaLocation":42},"/events/","events",{"text":294,"config":295},"Partners",{"href":296,"dataGaName":297,"dataGaLocation":42},"/partners/","partners",{"backgroundColor":299,"textColor":300,"text":301,"image":302,"link":306},"#2f2a6b","#fff","Insights for the future of software development",{"altText":303,"config":304},"the source promo card",{"src":305},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":307,"config":308},"Read the latest",{"href":309,"dataGaName":310,"dataGaLocation":42},"/the-source/","the source",{"text":312,"config":313,"lists":315},"Company",{"dataNavLevelOne":314},"company",[316],{"items":317},[318,323,329,331,336,341,346,351,356,361,366],{"text":319,"config":320},"About",{"href":321,"dataGaName":322,"dataGaLocation":42},"/company/","about",{"text":324,"config":325,"footerGa":328},"Jobs",{"href":326,"dataGaName":327,"dataGaLocation":42},"/jobs/","jobs",{"dataGaName":327},{"text":289,"config":330},{"href":291,"dataGaName":292,"dataGaLocation":42},{"text":332,"config":333},"Leadership",{"href":334,"dataGaName":335,"dataGaLocation":42},"/company/team/e-group/","leadership",{"text":337,"config":338},"Team",{"href":339,"dataGaName":340,"dataGaLocation":42},"/company/team/","team",{"text":342,"config":343},"Handbook",{"href":344,"dataGaName":345,"dataGaLocation":42},"https://handbook.gitlab.com/","handbook",{"text":347,"config":348},"Investor relations",{"href":349,"dataGaName":350,"dataGaLocation":42},"https://ir.gitlab.com/","investor relations",{"text":352,"config":353},"Trust Center",{"href":354,"dataGaName":355,"dataGaLocation":42},"/security/","trust center",{"text":357,"config":358},"AI Transparency Center",{"href":359,"dataGaName":360,"dataGaLocation":42},"/ai-transparency-center/","ai transparency center",{"text":362,"config":363},"Newsletter",{"href":364,"dataGaName":365,"dataGaLocation":42},"/company/contact/","newsletter",{"text":367,"config":368},"Press",{"href":369,"dataGaName":370,"dataGaLocation":42},"/press/","press",{"text":372,"config":373,"lists":374},"Contact us",{"dataNavLevelOne":314},[375],{"items":376},[377,380,385],{"text":49,"config":378},{"href":51,"dataGaName":379,"dataGaLocation":42},"talk to sales",{"text":381,"config":382},"Get help",{"href":383,"dataGaName":384,"dataGaLocation":42},"/support/","get help",{"text":386,"config":387},"Customer portal",{"href":388,"dataGaName":389,"dataGaLocation":42},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":391,"login":392,"suggestions":399},"Close",{"text":393,"link":394},"To search repositories and projects, login to",{"text":395,"config":396},"gitlab.com",{"href":56,"dataGaName":397,"dataGaLocation":398},"search login","search",{"text":400,"default":401},"Suggestions",[402,404,408,410,414,418],{"text":71,"config":403},{"href":76,"dataGaName":71,"dataGaLocation":398},{"text":405,"config":406},"Code Suggestions (AI)",{"href":407,"dataGaName":405,"dataGaLocation":398},"/solutions/code-suggestions/",{"text":123,"config":409},{"href":125,"dataGaName":123,"dataGaLocation":398},{"text":411,"config":412},"GitLab on AWS",{"href":413,"dataGaName":411,"dataGaLocation":398},"/partners/technology-partners/aws/",{"text":415,"config":416},"GitLab on Google Cloud",{"href":417,"dataGaName":415,"dataGaLocation":398},"/partners/technology-partners/google-cloud-platform/",{"text":419,"config":420},"Why GitLab?",{"href":84,"dataGaName":419,"dataGaLocation":398},{"freeTrial":422,"mobileIcon":427,"desktopIcon":432,"secondaryButton":435},{"text":423,"config":424},"Start free trial",{"href":425,"dataGaName":47,"dataGaLocation":426},"https://gitlab.com/-/trials/new/","nav",{"altText":428,"config":429},"Gitlab Icon",{"src":430,"dataGaName":431,"dataGaLocation":426},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":428,"config":433},{"src":434,"dataGaName":431,"dataGaLocation":426},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":436,"config":437},"Get Started",{"href":438,"dataGaName":439,"dataGaLocation":426},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/compare/gitlab-vs-github/","get started",{"freeTrial":441,"mobileIcon":445,"desktopIcon":447},{"text":442,"config":443},"Learn more about GitLab Duo",{"href":76,"dataGaName":444,"dataGaLocation":426},"gitlab duo",{"altText":428,"config":446},{"src":430,"dataGaName":431,"dataGaLocation":426},{"altText":428,"config":448},{"src":434,"dataGaName":431,"dataGaLocation":426},{"freeTrial":450,"mobileIcon":455,"desktopIcon":457},{"text":451,"config":452},"Back to pricing",{"href":204,"dataGaName":453,"dataGaLocation":426,"icon":454},"back to pricing","GoBack",{"altText":428,"config":456},{"src":430,"dataGaName":431,"dataGaLocation":426},{"altText":428,"config":458},{"src":434,"dataGaName":431,"dataGaLocation":426},"content:shared:en-us:main-navigation.yml","Main Navigation","shared/en-us/main-navigation.yml","shared/en-us/main-navigation",{"_path":464,"_dir":36,"_draft":6,"_partial":6,"_locale":7,"title":465,"button":466,"image":471,"config":475,"_id":477,"_type":28,"_source":30,"_file":478,"_stem":479,"_extension":33},"/shared/en-us/banner","is now in public beta!",{"text":467,"config":468},"Try the Beta",{"href":469,"dataGaName":470,"dataGaLocation":42},"/gitlab-duo/agent-platform/","duo banner",{"altText":472,"config":473},"GitLab Duo Agent Platform",{"src":474},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1753720689/somrf9zaunk0xlt7ne4x.svg",{"layout":476},"release","content:shared:en-us:banner.yml","shared/en-us/banner.yml","shared/en-us/banner",{"_path":481,"_dir":36,"_draft":6,"_partial":6,"_locale":7,"data":482,"_id":686,"_type":28,"title":687,"_source":30,"_file":688,"_stem":689,"_extension":33},"/shared/en-us/main-footer",{"text":483,"source":484,"edit":490,"contribute":495,"config":500,"items":505,"minimal":678},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":485,"config":486},"View page source",{"href":487,"dataGaName":488,"dataGaLocation":489},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":491,"config":492},"Edit this page",{"href":493,"dataGaName":494,"dataGaLocation":489},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":496,"config":497},"Please contribute",{"href":498,"dataGaName":499,"dataGaLocation":489},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":501,"facebook":502,"youtube":503,"linkedin":504},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[506,529,585,614,648],{"title":60,"links":507,"subMenu":512},[508],{"text":509,"config":510},"DevSecOps platform",{"href":69,"dataGaName":511,"dataGaLocation":489},"devsecops platform",[513],{"title":202,"links":514},[515,519,524],{"text":516,"config":517},"View plans",{"href":204,"dataGaName":518,"dataGaLocation":489},"view plans",{"text":520,"config":521},"Why Premium?",{"href":522,"dataGaName":523,"dataGaLocation":489},"/pricing/premium/","why premium",{"text":525,"config":526},"Why Ultimate?",{"href":527,"dataGaName":528,"dataGaLocation":489},"/pricing/ultimate/","why ultimate",{"title":530,"links":531},"Solutions",[532,537,539,541,546,551,555,558,562,567,569,572,575,580],{"text":533,"config":534},"Digital transformation",{"href":535,"dataGaName":536,"dataGaLocation":489},"/topics/digital-transformation/","digital transformation",{"text":148,"config":538},{"href":150,"dataGaName":148,"dataGaLocation":489},{"text":137,"config":540},{"href":119,"dataGaName":120,"dataGaLocation":489},{"text":542,"config":543},"Agile development",{"href":544,"dataGaName":545,"dataGaLocation":489},"/solutions/agile-delivery/","agile delivery",{"text":547,"config":548},"Cloud transformation",{"href":549,"dataGaName":550,"dataGaLocation":489},"/topics/cloud-native/","cloud transformation",{"text":552,"config":553},"SCM",{"href":133,"dataGaName":554,"dataGaLocation":489},"source code management",{"text":123,"config":556},{"href":125,"dataGaName":557,"dataGaLocation":489},"continuous integration & delivery",{"text":559,"config":560},"Value stream management",{"href":177,"dataGaName":561,"dataGaLocation":489},"value stream management",{"text":563,"config":564},"GitOps",{"href":565,"dataGaName":566,"dataGaLocation":489},"/solutions/gitops/","gitops",{"text":187,"config":568},{"href":189,"dataGaName":190,"dataGaLocation":489},{"text":570,"config":571},"Small business",{"href":194,"dataGaName":195,"dataGaLocation":489},{"text":573,"config":574},"Public sector",{"href":199,"dataGaName":200,"dataGaLocation":489},{"text":576,"config":577},"Education",{"href":578,"dataGaName":579,"dataGaLocation":489},"/solutions/education/","education",{"text":581,"config":582},"Financial services",{"href":583,"dataGaName":584,"dataGaLocation":489},"/solutions/finance/","financial services",{"title":207,"links":586},[587,589,591,593,596,598,600,602,604,606,608,610,612],{"text":219,"config":588},{"href":221,"dataGaName":222,"dataGaLocation":489},{"text":224,"config":590},{"href":226,"dataGaName":227,"dataGaLocation":489},{"text":229,"config":592},{"href":231,"dataGaName":232,"dataGaLocation":489},{"text":234,"config":594},{"href":236,"dataGaName":595,"dataGaLocation":489},"docs",{"text":257,"config":597},{"href":259,"dataGaName":5,"dataGaLocation":489},{"text":252,"config":599},{"href":254,"dataGaName":255,"dataGaLocation":489},{"text":261,"config":601},{"href":263,"dataGaName":264,"dataGaLocation":489},{"text":274,"config":603},{"href":276,"dataGaName":277,"dataGaLocation":489},{"text":266,"config":605},{"href":268,"dataGaName":269,"dataGaLocation":489},{"text":279,"config":607},{"href":281,"dataGaName":282,"dataGaLocation":489},{"text":284,"config":609},{"href":286,"dataGaName":287,"dataGaLocation":489},{"text":289,"config":611},{"href":291,"dataGaName":292,"dataGaLocation":489},{"text":294,"config":613},{"href":296,"dataGaName":297,"dataGaLocation":489},{"title":312,"links":615},[616,618,620,622,624,626,628,632,637,639,641,643],{"text":319,"config":617},{"href":321,"dataGaName":314,"dataGaLocation":489},{"text":324,"config":619},{"href":326,"dataGaName":327,"dataGaLocation":489},{"text":332,"config":621},{"href":334,"dataGaName":335,"dataGaLocation":489},{"text":337,"config":623},{"href":339,"dataGaName":340,"dataGaLocation":489},{"text":342,"config":625},{"href":344,"dataGaName":345,"dataGaLocation":489},{"text":347,"config":627},{"href":349,"dataGaName":350,"dataGaLocation":489},{"text":629,"config":630},"Sustainability",{"href":631,"dataGaName":629,"dataGaLocation":489},"/sustainability/",{"text":633,"config":634},"Diversity, inclusion and belonging (DIB)",{"href":635,"dataGaName":636,"dataGaLocation":489},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":352,"config":638},{"href":354,"dataGaName":355,"dataGaLocation":489},{"text":362,"config":640},{"href":364,"dataGaName":365,"dataGaLocation":489},{"text":367,"config":642},{"href":369,"dataGaName":370,"dataGaLocation":489},{"text":644,"config":645},"Modern Slavery Transparency Statement",{"href":646,"dataGaName":647,"dataGaLocation":489},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":649,"links":650},"Contact Us",[651,654,656,658,663,668,673],{"text":652,"config":653},"Contact an expert",{"href":51,"dataGaName":52,"dataGaLocation":489},{"text":381,"config":655},{"href":383,"dataGaName":384,"dataGaLocation":489},{"text":386,"config":657},{"href":388,"dataGaName":389,"dataGaLocation":489},{"text":659,"config":660},"Status",{"href":661,"dataGaName":662,"dataGaLocation":489},"https://status.gitlab.com/","status",{"text":664,"config":665},"Terms of use",{"href":666,"dataGaName":667,"dataGaLocation":489},"/terms/","terms of use",{"text":669,"config":670},"Privacy statement",{"href":671,"dataGaName":672,"dataGaLocation":489},"/privacy/","privacy statement",{"text":674,"config":675},"Cookie preferences",{"dataGaName":676,"dataGaLocation":489,"id":677,"isOneTrustButton":105},"cookie preferences","ot-sdk-btn",{"items":679},[680,682,684],{"text":664,"config":681},{"href":666,"dataGaName":667,"dataGaLocation":489},{"text":669,"config":683},{"href":671,"dataGaName":672,"dataGaLocation":489},{"text":674,"config":685},{"dataGaName":676,"dataGaLocation":489,"id":677,"isOneTrustButton":105},"content:shared:en-us:main-footer.yml","Main Footer","shared/en-us/main-footer.yml","shared/en-us/main-footer",[691],{"_path":692,"_dir":693,"_draft":6,"_partial":6,"_locale":7,"content":694,"config":697,"_id":699,"_type":28,"title":18,"_source":30,"_file":700,"_stem":701,"_extension":33},"/en-us/blog/authors/eric-eastwood","authors",{"name":18,"config":695},{"headshot":7,"ctfId":696},"MadLittleMods",{"template":698},"BlogAuthor","content:en-us:blog:authors:eric-eastwood.yml","en-us/blog/authors/eric-eastwood.yml","en-us/blog/authors/eric-eastwood",{"_path":703,"_dir":36,"_draft":6,"_partial":6,"_locale":7,"header":704,"eyebrow":705,"blurb":706,"button":707,"secondaryButton":711,"_id":713,"_type":28,"title":714,"_source":30,"_file":715,"_stem":716,"_extension":33},"/shared/en-us/next-steps","Start shipping better software faster","50%+ of the Fortune 100 trust GitLab","See what your team can do with the intelligent\n\n\nDevSecOps platform.\n",{"text":44,"config":708},{"href":709,"dataGaName":47,"dataGaLocation":710},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":49,"config":712},{"href":51,"dataGaName":52,"dataGaLocation":710},"content:shared:en-us:next-steps.yml","Next Steps","shared/en-us/next-steps.yml","shared/en-us/next-steps",1758326259085]