Gitlab hero border pattern left svg Gitlab hero border pattern right svg

Blog Handbook

The GitLab blog is managed by @rebecca the Managing Editor.



  1. Build credibility and authority.
  2. Increase awareness of GitLab the company, product, and community.
  3. Increase organic search rankings and traffic.
  4. Contribute to lead generation and revenue.


Mission statement: Create, curate, and elevate stories that increase awareness of the benefits of a single application for the entire DevOps lifecycle, as well as awareness of GitLab as a pioneer in all-remote work.

Please see Attributes of a successful blog post below for examples of stories that perform well on our blog.

The blog is not the permanent place for tutorials, which should live in the docs and should be linked to when relevant.

Publishing process

Time-sensitive posts: Official announcements, company updates, breaking changes, and news

If you need to publish a time-sensitive post, it is critical that you give the content team advance notice. Please follow the process outlined below:

  1. Start by opening an issue in the project, using the Blog post template and applying the priority label. Even if you do not have a draft or a confirmed publish date, it's important to open the issue as far in advance as possible and ping @rebecca so she knows it is coming and can prioritize accordingly.
  2. The issue title should reflect the date on which you expect to publish.
  3. The issue due date should be two working days before the publish date.
  4. If other due dates apply (for example, design assets are required) make sure the entire timeline and all the people responsible are captured in the issue description.
  5. In most cases, we expect that you or one of your team will write the post and create the merge request for it, with all images and other formatting included. Feel free to start your draft using this blog post template which has all the relevant information already embedded. Then see the formatting guidelines for help with creating and formatting your blog post MR.
  6. If you need assistance with drafting the post or creating the MR, please make this clear in your issue and we will confirm if this is possible within your timeframe.
  7. Use the Blog post merge request template for your MR and ensure it is set to close the associated issue automatically.
  8. Be sure to check the review app for your blog post or preview it locally to ensure images, headlines, etc. are formatted correctly before handing over.
  9. When your MR is ready for review, please assign it and the corresponding issue to @rebecca (or @vsilverthorne or @skassabian if Rebecca is OOO) a minimum of two working days ahead of when you expect to publish. Please submit your MR earlier if you can.
  10. When you've assigned your MR and issue to a reviewer, please change the due date on the issue to reflect the publishing date.
  11. Your reviewer may leave comments for you to address, in which case they will assign the MR back to you. When you have resolved all outstanding discussions, assign the MR back to the reviewer for final review and merging.

Who to assign your MR to – urgent posts

Please assign first to @rebecca. If she is OOO, assign to @vsilverthorne/@skassabian. If no one is available and you cannot wait until they return, please assign to a member of the Technical Writing team. Where possible, select the technical writer who is listed for the most relevant stage group. If you need immediate assistance/review, find a technical writer who is online on Slack to request this directly. Regardless, be sure to specify any time constraints around getting the content reviewed and posted.

Continuous delivery mindset: With time-sensitive posts, don't wait to publish a post if you have enough information to go live. It's OK to publish a headline and paragraph to get the news out in a timely matter and add more details later (e.g. add more graphics, charts, etc.). We don't want to miss out on a news cycle because we're waiting for an image or supplementary information.

Error budgets for time-sensitive posts

Inspired by error budgets used by Engineering.

Applied to the blog, error budgets incentivize forward planning and early communication of time-sensitive blog posts. This helps the Editorial team to minimize time spent on unplanned work, shuffling of the publishing schedule, or work that is subsequently wasted or does not serve our mission and vision, or the needs of our audience.

Each functional group is responsible for not exceeding an allowed budget of 15 points per quarter. The number of points given will depend on the severity of the impact to the team's workflow:

Where error budget points are incurred, a member of the Editorial team will apply the relevant label to the issue or merge request in question. Points will be totaled at the end of the quarter and communicated to functional groups who exceed their budgets, so that we can work together on solutions to prevent this in the future.

How to avoid incurring error budget points

Any other blog post ideas

If you'd like to write about something for the GitLab blog, please follow the instructions below to pitch your idea to the Editorial team. This process is intended to ensure the Editorial team is spending the bulk of their time working on posts that get results and align with the goals for the blog.

If you prefer, you can submit a complete blog post to the GitLab Unfiltered blog without pitching first. The Unfiltered blog is monitored weekly for posts to feature on the main blog.

How to pitch a blog post

Open an issue in the project, using the Blog post template. Include a short summary of your proposed blog post where indicated, and add the Blog::Pitch label to ensure your pitch is triaged.

If you do not intend to to write the post yourself but are just suggesting an idea you think would be worth pursuing, please make this clear in your issue description.

Note: If you're planning to write a tutorial post describing how to do something with GitLab, please ensure that the relevant documentation exists first before you write a blog post about it. A blog post should not replace documentation, but should add more information, context, and color around a technical subject, linking back to the documentation for full instructions.

Triage of pitches

The Editorial team will review issues with the label Blog::Pitch every Monday, and a team member will respond on your issue to let you know whether to move forward with your draft.

The team will be assessing pitches against the following criteria:

  1. The post idea aligns with the blog objectives and scope; and/or
  2. The idea shows potential to become a high-performing post

If your pitch doesn't meet these criteria, no problem! The Editorial team will suggest that you submit your post to the GitLab Unfiltered blog instead. The Unfiltered blog is monitored weekly for posts to feature on the main blog.

If your pitch is accepted

  1. Feel free to start your draft using this blog post template which has all the relevant information already embedded. Then see the formatting guidelines for help with creating and formatting your blog post MR.
  2. Use the Blog post merge request template for your MR and ensure it is set to close the associated issue automatically.
  3. Assign the issue and associated merge request to yourself and apply the appropriate labels based on what stage of creation you are on. Once it's ready for editorial review, assign both to @rebecca. She will assign a member of the Editorial team to review the post.

Note for the Content team: If your blog post is already scheduled and appears on the blog calendar please submit your MR and associated issue directly to @vsilverthorne or @skassabian for review.

If you have a blog post idea but do not intend to write it yourself, simply label the issue blog post and the content team will review during triage.

Blog post ideas and proposals for Content Hack Day should be created in the Content Hack Day project. We keep these issues in a separate project because not all ideas and proposals that come from Hack Day will be worked on.

Pick Your Brain posts

For follow-up posts to Pick Your Brain interviews with the CEO, as soon as an interview or livestream is scheduled, please open an issue in the project, using the PYB blog post template, and fill out the relevant information there.

The blog editorial team will assess the content of the post and decide how best to leverage it. This may mean publishing on the blog, on our Medium site, simply sharing the video on social, or something else.

Once agreed, the blog team will assign and communicate the expected publish date.


Publishing schedule

With the exception of time-sensitive announcements and news, we are aiming to have blog posts scheduled two weeks ahead.

Blog calendar

The calendar below documents when posts will be published, as well as industry awareness days and anniversaries we may cover. Please bear this in mind when requesting a specific publish date for a post.

Please note that all dates are subject to change to accommodate urgent posts and announcements.

Writing blog posts

The Content Marketing team is responsible for increasing sessions on the GitLab blog month over month. We use data-driven insights to decide what to write on using the content marketing dashboard. In order to hit our goals, we aim to publish at least 3 blog posts that will garner 10,000+ sessions.

*From 2018 blog analysis conducted in 2018-10

  1. Average sessions per month on the blog: ~30,000
  2. Average sessions per post in published month: 3,792
  3. Average sessions per content marketing post in published month: 3,500
  4. 55% of posts get <1,000 sessions in a month
  5. 27.47% of posts hit our expected outcome of 1,000-4,999 sessions in published month
  6. 28.57% of posts garner less than 499 sessions in published month
  7. 9% of posts are "hits" (10K+ sessions in published month); "Hits" don't consistently perform well over time

Breakdown by category of "hits":


  1. There is not a strong correlation between # of sessions and topic
  2. Strong performing content marketing posts focus on show and tell engineering stories
  3. Posts really fall into the 5,000-9,999 session bracket (3.3%)
  4. Content hack day posts tend to perform well (>1,000 sessions in published month)

Identifying and qualifying a high-performing blog post

Qualifying story ideas:

Look for the following patterns:

  1. Team implementing a new technology, process, or coding language
  2. Deep dive into how a popular feature is made
  3. Chronicling a performance improvement
  4. Covering a controversial decision that was made

Who and how to interview:

  1. Contact the technical subject matter expert. This can be someone who created the issue, or managed the project.
  2. Set up a 30 minute interview and dig into:
    • What was the challenge?
    • What was the solution?
    • How did you go from point a to point b? Walk me through your thought process.
    • How was the solution implemented and what is a realistic use case of the solution?
    • What lessons were learned?
    • How did this make the GitLab product / development community better?
  3. Optional: Contact the business subject matter expert. This could be a product manager or a product marketing manager.
  4. Set up a 30 minute interview and dig into:
    • How does this solution help a user?
    • What business value does this solution bring?
    • How does this solution relate to our product?

Attributes of a successful blog post:

  1. Deep dive into a hard technical challenge.
  2. A behind-the-scenes look at how we tackled something: "How we built x," "How we solved y," "How we scaled z."
  3. Puts the reader in the shoes of the person who faced the challenge; reader learns via compelling example.
  4. Intellectually satisfying; learning component.
  5. Allows the reader to learn from someone else's mistake, and follows a problem/trials and triumphs/solution story arc.
  6. Taking a controversial or unpopular stance on a topic, backed by hard evidence.

Examples of high-performing posts (20K+ sessions in published month):

  1. Meet the GitLab Web IDE
  2. How a fix in Go 1.9 sped up our Gitaly service by 30x
  3. Hey, data teams - We're working on a tool just for you
  4. Why we chose Vue.js
  5. How we do Vue: one year later

Team members can also browse the Content Marketing Dashboard to see more examples of current blog posts drawing high traffic.

Conducting a blog analysis

Pull information on all blog posts to document how many sessions each post received in the month, and how many sessions they received of all time. Categorize them by type, bracket, total sessions in month, total sessions to date, category, theme, and topic. Eventually add first touch point revenue data. Search Google Drive for Blog Performance to find the appropriate sheet to work from.

Column explanations:

Preparing confidential blog posts

In the event that we have a big announcement to make and the information must remain confidential until, use the forked project to prepare your merge request.

Publishing natively on LinkedIn and Medium

Occasionally, some blog posts are better suited to publishing natively by the author on LinkedIn, or on Medium. We reach slightly different audiences on these channels, and some content will get better exposure and reach if published there. If you submit a blog post and the content team decides it's a better fit for one of these, we will let you know. For now, we will treat this segmentation as an experiment, monitor the performance of different types of content on different channels, and apply our learnings. We may then be able to start planning content for specific outlets and commission stories accordingly.

Third-party posts

We will promote anyone integrating with GitLab, even if we compete with them. It is very important to demonstrate to our customers that we do not lock them in.

If you are a partner or industry-adjacent third party who wants to write for our blog, please contact the Partner Marketing team before proceeding.

Blog posts concerning third parties or partners, whether they are to be published on the GitLab blog or externally, should be proposed to the blog editorial team before agreeing.

As a rule, any blog post, regardless of the author, should offer some informational, educational, or entertainment value to the reader. We do not publish material that is exclusively promotional in nature.

Guest posts by GitLab partners

Official GitLab partners may use our CTA button and include promotional copy, however the blog post must still serve a function (informational or educational) other than simply promoting something.

For either type of guest post, the process and guidelines for publishing are as follows:

  1. Create an issue in the www-gitlab-com project and label it blog-post and guest/partner post.
  2. If technical input is required, we will ask for instructions from the third party; otherwise it is at the discretion of the blog editorial and technical writing teams whether or not to go forward.

Guest posts by non-partners

If the author of a guest post is not an official GitLab partner, they may link back to their website or own content with inline links, but may not include a CTA button or promotional copy.

Cross posts

We do not republish posts from other publications verbatim.

If you spot a tweet, post, or feedback anywhere detailing an interesting use case for GitLab which you think could make a good story, open an issue and ping @kimlock to help determine whether we should pursue an interview for a blog post or case study (we will not ask the original author to create something for us).

When we're doing a joint announcement and a partner wants to cross-post a GitLab article we have two requirements:

  1. The cross-posted article must open with attribution to GitLab as the source and that attribution must link back to the original article as described below under Crediting GitLab on a page.
  2. The cross-posted article page should also include a canonical link in its metadata as described below under under Crediting GitLab in metadata.

Crediting GitLab on a page

Crediting GitLab on a cross-posted article page is easy. The partner just needs to add a line like the following to their HTML source just above the article content:

<p>This post was originally published by GitLab on <a href=""></a>.</p>

For example, if the URL for the original article is then the attribution source code would look like so:

<p>This post was originally published by GitLab on <a href=""></a></p>

That would produce an attribution that looks about like this:

This post was originally published by GitLab on

Crediting GitLab in metadata

Adding a canonical link to a cross-posted article page's metadata is also easy. Just add a link element within the page's <head> element, add a rel attribute set to "canonical", and add an href attribute set to the original article URL.

For example, if the URL for the original article is then the link element should look like so:

<link rel="canonical" href="">

Formatting guidelines

Please see the publishing process before you get started.

To publish to the blog, you will need to create a merge request for the www-gitlab-com project with a file of your blog post content formatted in Markdown. Please read through the Markdown guide for reference.

If you like, you can start by drafting your post in a Google Doc (feel free to make a copy of this blog post template). Below are instructions for formatting the content of your file correctly. If you already have your content ready to go, you can jump ahead to see how to add your blog post file with a merge request.


The post frontmatter is the first part of any post. It is standard and cannot be changed, so please make sure to provide the correct information, and do not add nor remove anything from the default template. If you think one of the fields is unnecessary for your post, leave it blank and the editor who reviews your post may remove it.

title: "This is the post title"
author: Firstname Lastname # if name includes special characters use double quotes "First Last"
author_gitlab: username # ex: johndoe
author_twitter: Twitter username or gitlab # ex: johndoe
categories: company
image_title: '/images/blogimages/post-cover-image.jpg'
description: "Short description for the blog post"
tags: tag1, tag2, tag3
cta_button_text: 'Watch the XXX release webcast live!' # optional
cta_button_link: '' # optional
guest: true # required when the author is not a GitLab Team Member
ee_cta: false # required only if you do not want to display the EE-trial banner
install_cta: false # required only if you do not want to display the 'Install GitLab' banner
twitter_text: "Text to tweet" # optional;  If no text is provided it will use post's title.
featured: yes # reviewer should set
postType: content definition # i.e.: content marketing, product, corporate
merch_banner_destination_url: ""
merch_banner_image_source: "/images/merchandising-content/XXX.jpg"
merch_banner_body_title: ""
merch_banner_body_content: ""
merch_banner_cta_text: ""
merch_sidebar_destination_url: ""
merch_sidebar_image_source: "/images/merchandising-content/XXX.jpg"
merch_sidebar_body_title: ""
merch_sidebar_body_content: ""
merch_sidebar_cta_text: ""

More information about each field can be found below.

Cover image

Please see below for instructions on sizing, formatting, and storing images.

Add featured: yes to the frontmatter of a post to create a featured post. The two most recent featured posts will be shown at the top of the blog in the featured section (even if more are tagged). To remove a post from being featured, remove the featured: yes line from the frontmatter.


Use only one of the following categories per post. Do not change the capitalization, spelling, or anything else, otherwise you'll create another category, which is something we don't want to do accidentally.

We're working on improving category pages and tagging, but for now you can find posts under the same category by navigating to Dashes will be automatically added to multi-word categories and all of them will be lowercased in the URL. For example, the category "company" will be available under

Note that there's a CI test that checks for wrong or missing categories. If one of the above should change or a new category is added, don't forget to edit the following files:

  1. Rakefile (search for CATEGORIES) - responsible for the CI test
  2. source/includes/blog/category-nav.html.haml - responsible for the categories navbar
  3. source/handbook/marketing/blog/ - the handbook which you're currently reading
  4. If a category changes, you need to replace it in all posts. Here's a one-liner command you can run:

     find ./source/blog/posts -type f -exec sed -i 's/categories: old/categories: new/' {} \;

    where old is the old category and new is the new one.

  5. In case of a category change, a redirect should be added in (internal). This is the last step after all the above are merged into master and deployed. Ask a production engineer to help you.


The description meta tag is important for SEO, and is what appears on the blog index page. It is also part of Facebook Sharing and Twitter Cards. We set it up in the post frontmatter, as a short summary of what the post is about. Description is limited to 150, otherwise the text will be cut off on the index page.

It is mandatory for all the new posts, and it has been included in the default post frontmatter generated by the command rake new_post.


These are included to help readers find similar posts if they are interested in a particular subject. Tags appear at the bottom of each blog post, and clicking on a tag takes you to /blog/tags where you can view all tagged posts and browse by tag.

You can include as many tags as you like, separated by commas. Please only include tags from the following list, and note that they are case sensitive. If you think a tag should exist that isn't on this list, please leave a comment for the reviewer on your MR saying which tag you'd like to use and why it should be included.

GitLab Commit tags

In addition to the tags above, we also have tags for GitLab Commit events. If you want your blog post to appear on the relevant post-event GitLab Commit landing page, add the relevant tag to the frontmatter in the tags field. New tags will be added for each year of GitLab Commit.

Call to action

The CTA entry is optional; if you don't need to add any CTA to the hero, just omit both entries, leaving the frontmatter without them. Do not include any UTM parameters in the link. Always wrap their values with quotes.

The final result is a red button over the cover image of the post.

In the below example, the following was included in the blog post's frontmatter in order to generate the CTA button.

cta_button_text: 'Watch the 8.16 release webcast live!'
cta_button_link: ''

Hero CTA preview

EE trial banner

To not display the EE trial banner on the blog post, set ee_cta to false in the frontmatter. It is set to true by default, so there's no need to add ee_cta: true to the frontmatter. Only set to false on the rare occasion you have a strong reason to not display the banner (usually in the case of posts that are for developers and our open source community).

Post type

We use these to make it faster to track the effectiveness of different types of blog posts. There are three post type categories we use to differentiate blog content:

  1. Content marketing: Examples, education, reporting, storytelling, thought leadership, and use cases.
  2. Corporate: Company news, announcements, and community updates (ex: issue bash, contributor profiles).
  3. Product: Release posts, critical updates, and partnership announcements.

Use the postType frontmatter option to set the content definition.

Media embeds

We limit media embeds to the following providers:

Newsletter sign-up form

We can now embed a CTA for readers to sign up for our newsletter directly from the blog post page. There are two different designs. Use the following:

<%= partial "includes/blog/content-newsletter-cta", locals: { variant: "a" } %>


Newsletter sign-up CTA variant A


<%= partial "includes/blog/content-newsletter-cta", locals: { variant: "b" } %>


Newsletter sign-up CTA variant B

Note: this only works for blog post files with the extension

Click to tweet

Consider adding pull quotes as click-to-tweet boxes, to encourage and make it easier for readers to share key points from your blog post. This only works for blog post files with the extension

To add a click-to-tweet box, copy the below partial and enter the text and author fields. If the author doesn't have a Twitter account you can use gitlab instead.

<%= partial "includes/blog/tweet", locals: { text: '', author: '' } %>


  <%= partial "includes/blog/tweet", locals: { text: 'The Operations experience then should really just be that I go to work in the morning and see an email summary of what has happened, without me having to do anything', author: 'MarkPundsack' } %>


![Click to tweet](/images/handbook/marketing/click-to-tweet.png){: .shadow}

Merch banner and sidebar

These should be included by default on all blog posts. Use your discretion; if it doesn't seem appropriate to include merchandizing on a post (for example, if we're announcing a partnership or integration, or the blog post contains lots of images and a sidebar could distract from the content), you can leave out either the sidebar or both the sidebar and banner.

There are two steps to include these: Include all relevant fields in the frontmatter as shown in the example frontmatter above, and add the following items to the body of the blog post:

Merch sidebar

Add this line in between paragraphs at some point in the body of the blog post:

<%= partial "includes/blog/blog-merch-sidebar" %>

Merch banner

Add this line at the bottom of the blog post:

<%= partial "includes/blog/blog-merch-banner" %>

Note: These will only render if all the fields have been added correctly to the frontmatter as well.

You can see a live example of the merchandizing in this blog post: Why we created a Memory team at GitLab.

You can also view the raw file of our example post to see how all the necessary elements have been added to the frontmatter and body of the post.

Current merch items

Multicloud eBook
merch_banner_destination_url: "/resources/guide-to-the-cloud/"
merch_banner_image_source: "/images/merchandising-content/mc-guide-to-app-security-ebook-vertical.png"
merch_banner_body_title: "Guide to the Cloud"
merch_banner_body_content: "Harness the power of the cloud with microservices, cloud-agnostic DevOps, and workflow portability."
merch_banner_cta_text: "Learn more"
merch_sidebar_destination_url: "/resources/guide-to-the-cloud/"
merch_sidebar_image_source: "/images/merchandising-content/mc-guide-to-app-security-ebook-horizontal.png"
merch_sidebar_body_title: "Guide to the Cloud"
merch_sidebar_body_content: "Harness the power of the cloud with microservices, cloud-agnostic DevOps, and workflow portability."
merch_sidebar_cta_text: "Learn more"
Mastering your CI/CD webcast
merch_banner_destination_url: "/compare/github-actions-alternative/"
merch_banner_image_source: "/images/merchandising-content/mc-mastering-cicd-vertical.png"
merch_banner_body_title: "Master your CI/CD"
merch_banner_body_content: "Watch this webcast and learn to deliver faster with CI/CD."
merch_banner_cta_text: "View now"
merch_sidebar_destination_url: "/compare/github-actions-alternative/"
merch_sidebar_image_source: "/images/merchandising-content/mc-mastering-cicd-horizontal.png"
merch_sidebar_body_title: "Master your CI/CD"
merch_sidebar_body_content: "Watch the webcast"
merch_sidebar_cta_text: "View now"
CI/CD eBook
merch_banner_destination_url: "/resources/ebook-single-app-cicd"
merch_banner_image_source: "/images/merchandising-content/benefits-of-single-app-cicd.jpg"
merch_banner_body_title: "Free eBook: The benefits of single application CI/CD"
merch_banner_body_content: "Download the ebook to learn how you can utilize CI/CD without the costly integrations or plug-in maintenance."
merch_banner_cta_text: "Learn more"
merch_sidebar_destination_url: "/resources/ebook-single-app-cicd/"
merch_sidebar_image_source: "/images/merchandising-content/benefits-of-single-app-cicd.jpg"
merch_sidebar_body_title: "Single application CI/CD"
merch_sidebar_body_content: "How to reduce costly integrations and plug-in maintenance."
merch_sidebar_cta_text: "Learn more"
Security eBook
merch_banner_destination_url: "/resources/ebook-ciso-secure-software/"
merch_banner_image_source: "/images/merchandising-content/mc-10-steps-every-ciso-ebook-vertical.png"
merch_banner_body_title: "10 Steps Every CISO Should Take to Secure Next-Gen Software"
merch_banner_body_content: "Understand three software shifts impacting security, and the steps CISOs can take to protect their business."
merch_banner_cta_text: "Get the eBook"
merch_sidebar_destination_url: "/resources/ebook-ciso-secure-software/"
merch_sidebar_image_source: "/images/merchandising-content/mc-10-steps-every-ciso-ebook-horizontal.png"
merch_sidebar_body_title: "10 Steps to secure next-gen software"
merch_sidebar_body_content: "Learn how DevOps will impact your security program."
merch_sidebar_cta_text: "Get the eBook"


Comments are present in all posts by default. Set it to false only if you have a strong reason to do so (comments: false). They are our best source of feedback on posts.

Adding code blocks

Below are the two types of code blocks we commonly use on the blog. Find a number of other options in the Markdown guide.

In-line code

We use this for short words or phrases included in a paragraph. For inline code, surround the word or code with single backticks (`).


This is an `in-line` code block.

Results in:

This is an in-line code block.

Fenced code blocks

"Fenced" code blocks look like the block below. We use these for longer code snippets. To create a fenced code block, put triple backticks on one line directly above and one line directly below the code.


how to create fenced code block

Results in:

this is my code block
   here's another line

Highlighted code

Syntax highlighting helps make code easier to read. In order to enable syntax highlighting please append the language type at the end of the code block. The name matters because every language is highlighted differently.

Example (not highlighted):

```code goes here```
document.querySelectorAll('a[href^="#"]').forEach(elem => {
    elem.addEventListener('click', e => {
        let block = document.querySelector(elem.getAttribute('href')),
            offset = elem.dataset.offset ? parseInt(elem.dataset.offset) : 0,
            bodyOffset = document.body.getBoundingClientRect().top;
            top: block.getBoundingClientRect().top - bodyOffset + offset,
            behavior: "smooth"

Versus (highlighted javascript):

```code goes here```javascript

(or other languages/syntaxes such as yaml, ruby, sql, etc)

document.querySelectorAll('a[href^="#"]').forEach(elem => {
    elem.addEventListener('click', e => {
        let block = document.querySelector(elem.getAttribute('href')),
            offset = elem.dataset.offset ? parseInt(elem.dataset.offset) : 0,
            bodyOffset = document.body.getBoundingClientRect().top;
            top: block.getBoundingClientRect().top - bodyOffset + offset,
            behavior: "smooth"

Images and illustration

Blog images are stored in the source/images/blogimages/ directory. If your post contains many images, create a sub-directory for your post: source/images/blogimages/name-of-post/.

Preparing images

Image attribution

The only images accepted for are public domain images and screenshots. Whenever you choose an image which is not a screenshot, add a link to the original image to the merge request description and as an HTML comment:

<!-- image: image-url -->

Do the same for cover images, adding a link to the original image to the end of the post:

Cover image by [owner name and surname](link) on [Unsplash](link)
{: .note}

You can paste this from the box that appears on Unsplash when you have downloaded an image. For images not from Unsplash:

[Cover image](link-to-original-image) by [owner name and surname](link), licensed under [CC X](link-to-licence)
{: .note}

Inline images

To add an image inline, insert the following into your markdown file, where you want the image to appear:

![Alt text for your image](/images/blogimages/your-image-filename.jpg){: .shadow}

The alt text for your images should describe the content and function of your image for visitors using a screen reader.

Sizing and aligning images

There are new classes for sizing and positioning blog post images. They should be added to blog post images similar to how the shadow class is added already:

![Your image alt text](/images/blogimages/your-image-filename.jpg){: .shadow.small.left.wrap-text}

The new classes are grouped into:




.wrap-text: Only applies to images that are positioned left or right. Makes text wrap around the image.

These classes are only applied for screens 992px wide and wider.

If a Position class is used without an Effect class then there will simply be empty space where the image isn't. Similarly, using a Position class without a Size class will have little-to-no effect.

Image captions

Insert the following beneath an inline image to include a caption:

This is my image caption
{: .note.text-center}

This will look as below:

This is my image caption


For technical/tutorial posts, please illustrate your examples with code blocks or screenshots. Be consistent with your examples. E.g., if you are using a generic URL to exemplify your steps, be consistent and keep it, throughout the post.

Important security point: Do not expose your personal details by using your real tokens or security credentials. Use placeholders such as [project's CI token] stub instead. Or blur them if displayed on screenshots.

Embedding videos

Please see the Markdown Guide for instructions for embedding videos from YouTube and other sources.

If appropriate, please add a video credit, for example:

Video directed and produced by [Aricka Flowers](/company/team/#arickaflowers)
{: .note}

Embedding tweets or Instagram posts

Please see the Markdown guide for instructions for embedding posts from social media.

Creating GIFs

Animated GIFs are very useful to illustrate short dynamic processes, which might be easier to understand with this kind of resource. There are a few ways to create animated GIFs, one of them is using [Giphy Capture], a light-weight app for Mac.

Avoid GIFs with a huge file size, they will be difficult to load for users with bad internet connection. In those cases, you can either cut the GIFs in smaller pieces, or record a video, or use a sequential image.

Read more on Making Gifs in the Product Handbook.


If you create your own cover image, it should have the following proportions:

Try to have them harmonically aligned with the title, which overlays the background image in both cases. To crop the image, use the size of 1275x750 px. If you want to align the background image with the title overlay, use the widescreen proportion.

When your post is formatted and you're ready to create your merge request

You can go about this a couple of ways: by adding a new file to /source/blog/posts/ in the UI, or using the terminal on your own computer.

Creating a blog post MR from the UI

You should have an issue for the blog post you are writing. Go to the issue and click Create merge request.

Create merge request button

Now you have a merge request! Please edit the title to be the same as your blog post title, but leave WIP in front.

Make sure you select the blog post template for your MR, as this contains some important tasks for you to complete.

MR template for blog post

You can assign the MR to yourself while you are working on it.

Click on the link to the right of Request to merge (this is the name of your branch).

branch name

Now navigate to /source/blog/posts/ in the tree view, then click on the + dropdown menu and select New file.

New file

Enter your filename using the format This will become the URL for the blog post so please keep that in mind and don't use something like my-blog-post-draft! 😄

New file

Please do not include special characters, capital letters, or numbers (other than the date) in your filename as this can lead to issues with publishing and can break the link to your review app.

If you aren't sure when the post will be published, just choose an arbitrary date in the future – we will update it before publishing.

Now you can paste your post content into the window. If pasting from a Google Doc or another word processor, please make sure you convert it to plain text first. A quick way to do this is to paste everything into the URL bar of a browser, and paste it into your blog post file from there.

Example blog post file Note that there is no blank line at the top or lines in between your front matter items.

When ready, scroll down and enter a description of what you've just done in the Commit message box. Make sure the Target branch is correct (i.e. has the number and title of the issue for your blog post) then click Commit changes.

commit message

You will then see your added file. To go back to your MR, it's easiest to go to your MRs in your dashboard (this is why it's important to assign the MR to yourself when you create it). You'll find all MRs assigned to you in the top right corner of your screen.

MR dashboard

In your MR, if you scroll down and go to the Changes tab, you should see the blog post file you added there.

Changes tab

To see a preview of your post as it will look when it's live, you will need to wait for the pipeline to pass. You can check its progress from the Pipelines tab. When it passes, you can click on View app near the top of your MR and it will open a new tab with your preview. Please check your review app before asking anyone else to review.

Link to review app

If you need to make changes to your file, you can edit it by clicking on the edit button, which will take you to the same interface as when you added your new file. Just make your changes, edit the commit message to say what you did and hit Commit changes.

Edit button

When you're ready, assign the MR to a fellow team member to review your post. When they have reviewed and you've addressed any comments they had and resolved any outstanding discussions, please assign your MR and the corresponding issue to @rebecca to review.

You may find this video walkthrough helpful for creating your blog post MR from the UI:

Creating a post from the command line

You will need to be set up to edit the website locally in order to create your post this way. Please see GitLab 102 for instructions. If you're already set up:

rake new_post

Hit enter or return, then you'll be prompted to enter the post title. Type in the title exactly as you want it, for example "Hello World – I'm a new post" and rake will take care of the filename for you. Then you just open the file, fill out the front matter and start writing, or paste your (plain text) draft in from your Google Doc.

Adding a cover image to your blog post MR

Most blog posts require a new cover image. You should upload your chosen image to the same branch as your blog post file, so that it is all included in the same MR. This means it will be published to the website simultaneously, and also allows you to preview your cover image in the review app.

Step 1: Upload image file

When you have selected an image to use, save it on your computer, ensuring you don't include any capital letters, spaces, or special characters in the filename.

Now go to your MR and click on the branch name afterRequest to merge.

branch name

Navigate to source/images/blogimages/ in the tree view, then click on the + dropdown menu and select Upload file

Upload file

Drag and drop or click to upload your image file, then enter a description of what you've just done in the Commit message box. Make sure the Target branch is correct (i.e. has the number and title of the issue for your blog post) then click Upload file.

To go back to your MR, it's easiest to go to your MRs in your dashboard (this is why it's important to assign the MR to yourself when you create it). You'll find all MRs assigned to you in the top right corner of your screen.

MR dashboard

In your MR, if you scroll down and go to the Changes tab, you should see the blog post file you added there.

Changes tab

Step 2: Add file path to blog post file

Make a note of the name of your image.

Image file name

Scroll down to your blog post file and click on the pencil icon (Edit file button).

Edit button

This will take you to the same interface as when you added your new file. In the frontmatter, look for this field:

image_title: '/images/blogimages/post-cover-image.jpg'

Change the image filename to the name of the image you just uploaded. Ensure that you keep the whole path the same (so, starting with /images/blogimages/). Now edit the commit message to say what you did and hit Commit changes.

Go back to your MR. If everything has been done correctly, when the pipeline passes your review app should show the cover image as expected.

Link to review app

What if my cover image isn't showing?

This is usually because the image filename and what you entered in the frontmatter do not correspond. Look out for typos in either the filename or in the frontmatter.

Make sure that the image path is correct (i.e. it begins with /images/blogimages/ – no missing slash at the front, no source before the first slash).

It often helps to look at the file of another recent blog post that is already live and compare it to your file to see if there are any discrepancies.