How to Use Blogger API to Post on Blogger: A Step-by-Step Guide

The Blogger API allows developers to programmatically interact with Blogger blogs, enabling tasks such as creating, updating, and deleting posts, managing comments, and retrieving blog information. This guide will focus on posting content.


1. Prerequisites

Before you begin, ensure you have the following:

  • A Google Account: Necessary for accessing Google Cloud and Blogger.
  • A Blogger Blog: The blog you intend to post to.
  • Basic understanding of APIs: Familiarity with concepts like REST, HTTP methods (GET, POST, PUT, DELETE), and JSON.
  • Python (or your preferred programming language): This guide will provide Python examples, but the concepts apply to other languages as well.


2. Set Up Your Google Cloud Project

  1. All Google API interactions require a project in Google Cloud Platform (GCP).
  2. Go to Google Cloud Console: Navigate to console.cloud.google.com.
  3. Create a New Project:
    1. Click on the project dropdown at the top of the page (usually displays "My First Project" or your current project name).
    2. Click "New Project".
    3. Give your project a meaningful name (e.g., "Blogger API Integration") and click "Create".
  4. Enable the Blogger API:
    1. Once your project is created, use the navigation menu (☰) on the left.
    2. Go to APIs & Services > Library.
    3. Search for "Blogger API".
    4. Click on "Blogger API" in the search results.
    5. Click the "Enable" button.


3. Create OAuth 2.0 Credentials

To access user data (like posting to your blog), your application needs authorization using OAuth 2.0.

  1. Go to Credentials Page: In the Google Cloud Console, navigate to APIs & Services > Credentials.
  2. Create Credentials:
    1. Click on "CREATE CREDENTIALS" at the top.
    2. Select "OAuth client ID".
  3. Configure Consent Screen: If you haven't done this before, you'll be prompted to configure your OAuth consent screen.
    1. Choose "External" for User Type and click "CREATE".
    2. Fill in the required fields:
      1. App name: A user-facing name for your application (e.g., "My Blogger Poster").
      2. User support email: Your email address.
      3. Developer contact information: Your email address.
    3. Click "SAVE AND CONTINUE".
    4. (Optional) Scopes: While you can add scopes here, it's often easier to define them in your code. For Blogger, you'll need the https://www.googleapis.com/auth/blogger scope.
    5. (Optional) Test users: Add your Google account as a test user.
    6. Review and go back to Dashboard.
  4. Create OAuth Client ID (Continued):
    1. After configuring the consent screen, go back to Credentials > Create Credentials > OAuth client ID.
    2. Application type:
      1. For a script running on your computer, choose Desktop app.
      2. For a web application, choose Web application and set authorized redirect URIs (e.g., http://localhost:8080 for local development, or your actual application's redirect URI).
    3. Give it a name (e.g., "Blogger Desktop Client").
    4. Click "CREATE".
  5. Download Credentials: A dialog will appear showing your Client ID and Client Secret. Download the JSON file (or copy the ID and Secret) and keep it secure. This file contains sensitive information.


4. Install Google Client Library (Python Example)

Google provides client libraries for various programming languages to simplify API interactions.

For Python, install the google-api-python-client and google-auth-oauthlib packages:

pip install google-api-python-client google-auth-oauthlib


5. Authentication Flow (Python Example)

The first step in using the API is to authenticate your application and obtain access tokens. This typically involves an OAuth 2.0 flow where the user grants permission to your application.

Place your downloaded client_secret_YOUR_CLIENT_ID.json file in the same directory as your Python script, or rename it to client_secret.json.

import os
import pickle
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
# If modifying these scopes, delete the file token.pickle.
SCOPES = ['https://www.googleapis.com/auth/blogger']
def get_authenticated_service():
    creds = None
    # The file token.pickle stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists('token.pickle'):
        with open('token.pickle', 'rb') as token:
            creds = pickle.load(token)
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            # Point to your downloaded client_secret.json file
            flow = InstalledAppFlow.from_client_secrets_file(
                'client_secret.json', SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open('token.pickle', 'wb') as token:
            pickle.dump(creds, token)
    
    # Build the Blogger service object
    service = build('blogger', 'v3', credentials=creds)
    return service
# Example usage (don't run this directly, it's just for demonstration)
# service = get_authenticated_service()
# print("Authentication successful!")


Explanation of the get_authenticated_service function:

  • It checks for an existing token.pickle file, which stores your refresh token. This allows your application to authenticate without prompting you to log in every time.
  • If token.pickle doesn't exist or the credentials are invalid, it initiates the OAuth 2.0 flow using InstalledAppFlow. This will open a browser window for you to log in to your Google account and grant permissions.
  • Upon successful authorization, an access_token and refresh_token are obtained. The refresh_token is saved to token.pickle for future use.
  • Finally, it builds and returns the blogger service object, which you'll use to make API calls.


6. Get Your Blog ID

Most Blogger API operations require your Blog ID. You can find this in your Blogger dashboard URL or by using the API.

Method 1: From Blogger Dashboard URL

When you are logged into your Blogger dashboard and viewing your blog's posts, the URL will look something like this:

https://www.blogger.com/blog/posts/YOUR_BLOG_ID

The long string of numbers after /blog/posts/ is your Blog ID.

Method 2: Using the API

You can use the blogs.getByUrl method or blogs.listByUser method to retrieve your blog ID.

# Assuming 'service' is the authenticated Blogger service object
def get_blog_id(service, blog_url=None):
    if blog_url:
        try:
            blog = service.blogs().getByUrl(url=blog_url).execute()
            print(f"Found blog '{blog['name']}' with ID: {blog['id']}")
            return blog['id']
        except Exception as e:
            print(f"Error getting blog by URL: {e}")
            return None
    else:
        try:
            # List blogs for the authenticated user
            blogs = service.blogs().listByUser(userId='self').execute()
            if 'items' in blogs:
                for blog in blogs['items']:
                    print(f"Blog Name: {blog['name']}, ID: {blog['id']}, URL: {blog['url']}")
                # You might want to let the user choose or pick the first one
                if blogs['items']:
                    return blogs['items'][0]['id'] # Return the ID of the first blog found
            else:
                print("No blogs found for this user.")
            return None
        except Exception as e:
            print(f"Error listing blogs: {e}")
            return None
# --- Main execution example ---
if __name__ == '__main__':
    service = get_authenticated_service()
    
    # Replace with your actual blog URL or leave None to list all blogs
    # blog_id = get_blog_id(service, blog_url="https://yourblogname.blogspot.com/") 
    blog_id = get_blog_id(service) # This will list all your blogs and pick the first one
    if blog_id:
        print(f"\nUsing Blog ID: {blog_id}")
    else:
        print("\nCould not determine Blog ID. Exiting.")
        exit()


7. Post a New Blog Post

To create a new post, you'll use the posts.insert method.

# Assuming 'service' and 'blog_id' are already defined from previous steps
def create_blog_post(service, blog_id, title, content, labels=None, is_draft=False):
    """
    Creates a new blog post.
    Note: Custom permalinks (URLs) and individual post search descriptions
    cannot be reliably set via the Blogger API.
    """
    post_body = {
        'kind': 'blogger#post',
        'blog': {'id': blog_id}, # Associate the post with the specific blog ID
        'title': title,
        'content': content, # HTML content is allowed here
        'labels': labels if labels else [], # Optional list of strings for labels
        'status': 'DRAFT' if is_draft else 'LIVE' # Set to 'LIVE' for immediate publishing
    }
    # IMPORTANT: The 'customMetaData' field in the Blogger API is not currently used
    # by Blogger for the "Search Description" (meta description) of individual posts.
    # While you can send data to this field via the API, it will not appear
    # in the Blogger UI's "Search description" box or be used by search engines for that post.
    # To set a search description, you must do it manually in the Blogger UI
    # after enabling the "Search description" setting for your blog.
    
    # The 'url' field is read-only and cannot be set via the API.
    # Blogger automatically generates the permalink.
    try:
        print(f"\nAttempting to create post: '{title}'...")
        request = service.posts().insert(blogId=blog_id, body=post_body, isDraft=is_draft)
        response = request.execute()
        print(f"Successfully created post: '{response['title']}' (ID: {response['id']})")
        print(f"View it at: {response['url']}")
        return response
    except Exception as e:
        print(f"Error creating post: {e}")
        return None
# --- Main execution example ---
if __name__ == '__main__':
    # ... (code for getting service and blog_id from Section 5 & 6) ...
    # Example: Create a new post
    new_post_title = "My First Post from Blogger API"
    new_post_content = "This is a **bold** new post published programmatically using the Blogger API. " \
                       "You can include <i>HTML</i> content here."
    new_post_labels = ["API", "Python", "Automation"]
    # new_post_search_description = "A test post created using the Blogger API with custom search description. This will NOT show up." # Removed as it's not functional
    created_post = create_blog_post(
        service,
        blog_id,
        new_post_title,
        new_post_content,
        new_post_labels,
        is_draft=False # Set to True to save as draft
    )
    if created_post:
        # You can use created_post['id'] for subsequent updates/deletions
        post_id_to_update_or_delete = created_post['id']


8. Update a Blog Post

To update an existing post, you'll need its post_id and use the posts.update method.

# Assuming 'service' and 'blog_id' are already defined from previous steps
def update_blog_post(service, blog_id, post_id, new_title=None, new_content=None, new_labels=None):
    update_body = {
        'kind': 'blogger#post',
        'id': post_id, # This is crucial for updating the specific post
    }
    if new_title:
        update_body['title'] = new_title
    if new_content:
        update_body['content'] = new_content
    if new_labels is not None: # Allow empty list to clear labels
        update_body['labels'] = new_labels
    try:
        request = service.posts().update(blogId=blog_id, postId=post_id, body=update_body)
        response = request.execute()
        print(f"\nSuccessfully updated post: '{response['title']}' (ID: {response['id']})")
        print(f"View updated post at: {response['url']}")
        return response
    except Exception as e:
        print(f"\nError updating post: {e}")
        return None
# --- Main execution example ---
if __name__ == '__main__':
    # ... (code for getting service, blog_id, and potentially creating a post to get its ID) ...
    # Example: Update the created post
    if 'post_id_to_update_or_delete' in locals(): # Check if the variable exists
        updated_post_content = "This post has been <i>updated</i> programmatically. " \
                               "Adding more content and a new label."
        updated_post_labels = ["API", "Python", "Updated"]
        updated_post = update_blog_post(
            service,
            blog_id,
            post_id_to_update_or_delete,
            new_title="Updated Title from API!",
            new_content=updated_post_content,
            new_labels=updated_post_labels
        )


9. Delete a Blog Post

To delete a post, you'll need its post_id and use the posts.delete method.

# Assuming 'service' and 'blog_id' are already defined from previous steps
def delete_blog_post(service, blog_id, post_id):
    try:
        request = service.posts().delete(blogId=blog_id, postId=post_id)
        request.execute()
        print(f"\nSuccessfully deleted post with ID: {post_id}")
        return True
    except Exception as e:
        print(f"\nError deleting post: {e}")
        return False
# --- Main execution example ---
if __name__ == '__main__':
    # ... (code for getting service, blog_id, and potentially creating/updating a post) ...
    # Example: Delete the post
    # IMPORTANT: Use this with caution! It permanently deletes the post.
    if 'post_id_to_update_or_delete' in locals(): # Check if the variable exists
        confirm_delete = input(f"\nDo you want to delete post ID {post_id_to_update_or_delete}? (yes/no): ").lower()
        if confirm_delete == 'yes':
            delete_blog_post(service, blog_id, post_id_to_update_or_delete)
        else:
            print("Deletion cancelled.")


Complete Python Script Example

Here's a consolidated example of the Python script integrating all the functions:

import os
import pickle
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
import json
from datetime import datetime 
# If modifying these scopes, delete the file token.pickle.
SCOPES = ['https://www.googleapis.com/auth/blogger']
def get_authenticated_service():
    """
    Authenticates the user and returns the Blogger API service object.
    Uses token.pickle to store/retrieve credentials for subsequent runs.
    """
    creds = None
    # Check if a token file exists
    if os.path.exists('token.pickle'):
        with open('token.pickle', 'rb') as token:
            creds = pickle.load(token)
    
    # If credentials are not valid or don't exist, initiate the OAuth flow
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            # Refresh token if expired
            creds.refresh(Request())
        else:
            # Load client secrets from the downloaded JSON file
            # Make sure client_secret.json is in the same directory
            if not os.path.exists('client_secret.json'):
                print("Error: 'client_secret.json' not found. Please download it from Google Cloud Console.")
                print("Go to APIs & Services > Credentials, download OAuth Client ID JSON, and rename it to client_secret.json.")
                return None
            
            flow = InstalledAppFlow.from_client_secrets_file(
                'client_secret.json', SCOPES)
            creds = flow.run_local_server(port=0) # Runs a local server for the OAuth redirect
        
        # Save the credentials for the next run
        with open('token.pickle', 'wb') as token:
            pickle.dump(creds, token)
    
    # Build the Blogger service object
    service = build('blogger', 'v3', credentials=creds)
    return service
def get_blog_id(service, blog_url=None):
    """
    Retrieves the Blog ID. Can get by URL or list all blogs for the user.
    """
    if blog_url:
        try:
            blog = service.blogs().getByUrl(url=blog_url).execute()
            print(f"Found blog '{blog['name']}' with ID: {blog['id']}")
            return blog['id']
        except Exception as e:
            print(f"Error getting blog by URL '{blog_url}': {e}")
            return None
    else:
        try:
            print("\nAttempting to list blogs for the authenticated user...")
            # List blogs for the authenticated user
            blogs = service.blogs().listByUser(userId='self').execute()
            if 'items' in blogs:
                print("Available Blogs:")
                for i, blog in enumerate(blogs['items']):
                    print(f"  {i+1}. Name: {blog['name']}, ID: {blog['id']}, URL: {blog['url']}")
                
                if blogs['items']:
                    # Simple selection for demonstration; in a real app, you might have a UI
                    while True:
                        try:
                            choice = input("Enter the number of the blog you want to use (or 'q' to quit): ")
                            if choice.lower() == 'q':
                                return None
                            idx = int(choice) - 1
                            if 0 <= idx < len(blogs['items']):
                                selected_blog = blogs['items'][idx]
                                print(f"Selected Blog: '{selected_blog['name']}' (ID: {selected_blog['id']})")
                                return selected_blog['id']
                            else:
                                print("Invalid number. Please try again.")
                        except ValueError:
                            print("Invalid input. Please enter a number or 'q'.")
            else:
                print("No blogs found for this user.")
            return None
        except Exception as e:
            print(f"Error listing blogs: {e}")
            return None
def create_blog_post(service, blog_id, title, content, labels=None, is_draft=False):
    """
    Creates a new blog post.
    Note: Custom permalinks (URLs) and individual post search descriptions
    cannot be reliably set via the Blogger API.
    """
    post_body = {
        'kind': 'blogger#post',
        'blog': {'id': blog_id}, # Associate the post with the specific blog ID
        'title': title,
        'content': content, # HTML content is allowed here
        'labels': labels if labels else [], # Optional list of strings for labels
        'status': 'DRAFT' if is_draft else 'LIVE' # Set to 'LIVE' for immediate publishing
    }
    # IMPORTANT: The 'customMetaData' field in the Blogger API is not currently used
    # by Blogger for the "Search Description" (meta description) of individual posts.
    # While you can send data to this field via the API, it will not appear
    # in the Blogger UI's "Search description" box or be used by search engines for that post.
    # To set a search description, you must do it manually in the Blogger UI
    # after enabling the "Search description" setting for your blog.
    
    # The 'url' field is read-only and cannot be set via the API.
    # Blogger automatically generates the permalink.
    try:
        print(f"\nAttempting to create post: '{title}'...")
        request = service.posts().insert(blogId=blog_id, body=post_body, isDraft=is_draft)
        response = request.execute()
        print(f"Successfully created post: '{response['title']}' (ID: {response['id']})")
        print(f"View it at: {response['url']}")
        return response
    except Exception as e:
        print(f"Error creating post: {e}")
        return None
def update_blog_post(service, blog_id, post_id, new_title=None, new_content=None, new_labels=None):
    """
    Updates an existing blog post by its ID.
    """
    update_body = {
        'kind': 'blogger#post',
        'id': post_id, # Crucial: specifies which post to update
    }
    if new_title:
        update_body['title'] = new_title
    if new_content:
        update_body['content'] = new_content
    if new_labels is not None: # Allow passing an empty list to clear labels
        update_body['labels'] = new_labels
    try:
        print(f"\nAttempting to update post ID: {post_id}...")
        request = service.posts().update(blogId=blog_id, postId=post_id, body=update_body)
        response = request.execute()
        print(f"Successfully updated post: '{response['title']}' (ID: {response['id']})")
        print(f"View updated post at: {response['url']}")
        return response
    except Exception as e:
        print(f"Error updating post: {e}")
        return None
def delete_blog_post(service, blog_id, post_id):
    """
    Deletes a blog post by its ID. This action is irreversible.
    """
    try:
        print(f"\nAttempting to delete post ID: {post_id}...")
        request = service.posts().delete(blogId=blog_id, postId=post_id)
        request.execute()
        print(f"Successfully deleted post with ID: {post_id}")
        return True
    except Exception as e:
        print(f"Error deleting post: {e}")
        return False
# --- Main execution flow ---
if __name__ == '__main__':
    service = get_authenticated_service()
    if not service:
        exit() # Exit if authentication failed
    # Option 1: Provide your blog URL directly
    # blog_id = get_blog_id(service, blog_url="https://yourblogname.blogspot.com/")
    
    # Option 2: List all your blogs and select one
    blog_id = get_blog_id(service) 
    if not blog_id:
        print("Blog ID not determined. Exiting.")
        exit()
    # --- Step 1: Create a new post ---
    new_post_title = "My Auto-Generated Test Post"
    new_post_content = """
    <p>This is a <strong>test post</strong> created using the <b>Blogger API</b> with Python.</p>
    <p>Please remember that custom search descriptions cannot be set via the API.</p>
    <ul>
        <li>Item 1</li>
        <li>Item 2</li>
    </ul>
    """
    new_post_labels = ["Automation", "Python", "API Test"]
    created_post = create_blog_post(
        service,
        blog_id,
        new_post_title,
        new_post_content,
        new_post_labels,
        is_draft=False # Set to True to save as draft
    )
    if created_post:
        post_id_to_manage = created_post['id']
        
        # --- Step 2: Update the created post after a brief pause ---
        input("\nPress Enter to update the post...") # Wait for user
        updated_title = "Updated Title: API Automation in Action!"
        updated_content = """
        <p>This post has been <em>updated</em> using the API.</p>
        <p>New content has been added to demonstrate the <b>update</b> functionality.</p>
        <p><i>Revised on:</i> """ + str(datetime.now()) + """</p>
        """
        updated_labels = ["Automation", "Python", "API Test", "Updated"]
        update_blog_post(
            service,
            blog_id,
            post_id_to_manage,
            new_title=updated_title,
            new_content=updated_content,
            new_labels=updated_labels
        )
        # --- Step 3: Delete the post after another brief pause ---
        input("\nPress Enter to attempt to delete the post (this is irreversible)...") # Wait for user
        confirm_delete = input(f"Are you sure you want to delete post '{updated_title}' (ID: {post_id_to_manage})? (yes/no): ").lower()
        if confirm_delete == 'yes':
            delete_blog_post(service, blog_id, post_id_to_manage)
        else:
            print("Deletion cancelled.")
    else:
        print("Post creation failed, cannot proceed with update/delete steps.")
    print("\nScript finished.")

This article has been co-authored with Gemini - as we explored various options to carry out the task. It may still have errors, especially in parts other than posting. We also tried to include Permalink and Search Description, but finally concluded that the option isn't currently available/functional.
0 Comments.
Start the discussion!
Comment using Facebook
Explore Related Topics
What others like...
Keyboard Shortcuts for Posting with Blogger
Keyboard Shortcuts for Posting with Blogger
http://www.google.com/support/blogger/bin/answer.py?hl=en&answer=42197
Blogger Template: Contrasting Shades of Gray
Blogger Template: Contrasting Shades of Gray
Download || Demo This is a modification of one of the most beautiful Online Art Gallery blog templates available today. What makes it even more un…
Are you ready for a Happy New Year?
Are you ready for a Happy New Year?
HD Wallpaper: 2560 X 1600 px Happy New Year Folks! Have a blessed festive season :) Join Visual Cerebration ( http://visualcerebration.blogspot.c…
Blogger Template: Shades of Gray 2 (3+2 Column)
Blogger Template: Shades of Gray 2 (3+2 Column)
Download || Demo This is a modification of one of the most beautiful Online Art Gallery blog templates available today. What makes it even more un…
Blogger Template: Shades of Gray Ver 2.0
Blogger Template: Shades of Gray Ver 2.0
Download || Demo The most beautiful Online Art Gallery template available today gets even better. More sophisticated, more stylish, more functiona…
Resources >> Images >> Missing Photo Placeholder - Dark
Resources >> Images >> Missing Photo Placeholder - Dark

Blogger Template: Shades of Gray
Blogger Template: Shades of Gray
Download || Demo This is one of the most beautiful Online Art Gallery blog templates available today. What makes it even more unique is the 4 colu…
Blogger Template: Bluetiful Silver
Blogger Template: Bluetiful Silver
Download || Demo Bluetiful White was the second template designed by M S Ahluwalia from scratch. He decided to revisit it and here is the result B…
Blogger Template: Indigo Fresh
Blogger Template: Indigo Fresh
Download || Demo This is one of the most beautiful 4-Column blog templates available today. A highly sophisticated and beautiful yet simple design…

Are you looking for Real Happiness?

With a focus on positive psychology and passion for spreading happiness in the world, The Real Happiness Center is helping people find out what happiness means to them and how they can achieve it.

Are you a Mental Health Leader?

If you are a psychologist, psychiatrist, counselor, therapist, educator or a mental health institution administrator we can help you improve your client satisfaction and generate higher revenues.

Contact Us