Pages

  • Beranda

Muslim Online

  • Home
  • Menu1
    • Submenu1
    • Submenu2
    • Submenu3
  • Menu2
    • Submenu1
    • Submenu2
    • Submenu3
  • Menu3
  • Menu4
Home » Uncategories » Using WordPress as a Data-Entry Site to Power a Central API

Using WordPress as a Data-Entry Site to Power a Central API

— Selasa, 17 Desember 2024 — Add Comment

One of the areas where WordPress shines is content management. This is also corroborated by the fact that it is the world's leading Content Management System, or CMS, by numbers. The most popular way of displaying the content hosted by WordPress is…
Read on blog or Reader
Site logo image WordPress.com News Read on blog or Reader

Using WordPress as a Data-Entry Site to Power a Central API

By Antonius Hegyes on December 17, 2024

One of the areas where WordPress shines is content management. This is also corroborated by the fact that it is the world's leading Content Management System, or CMS, by numbers.

The most popular way of displaying the content hosted by WordPress is through a frontend, like through blog posts and pages. That is wonderful for human visitors but there are other ways of consuming that content –– for example, web APIs. 

In this article, we'll explore how we can leverage WordPress in order to power a central API for projects like phone apps, browser extensions, or the frontends of other WordPress sites!

Following along

All the steps described in this article were made in WordPress Playground. If you want to see the end result and maybe sometimes skip ahead as we go, download this ZIP file, and perform these steps:

  • Visit https://playground.wordpress.net/
  • Restore the site from the ZIP file
  • Log in into the admin area
Play video

Play video

What is a web API?

API stands for Application Programming Interface and it's a way for software applications to communicate with each other in a standardized fashion. A web API is simply one that is accessed through "the internet" –– for example, by entering a certain URL in your web browser.

There are multiple types of web APIs, and one common way to group them is the protocol they use. In this article, we'll be implementing two APIs, one based on the REST protocol, and another based on the GraphQL protocol. Other protocols you might have heard of include SOAP, RPC, or gRPC. 

WordPress actually includes a built-in REST API which powers the Gutenberg Block Editor. As of October 2024, the popular WPGraphQL plugin has become a canonical plugin paving the road for an official GraphQL API as well.

What data we'll be modeling

By the end of this article, we'll have built a WordPress site that allows users to login in order to add/update/delete data entries which will be queryable both via REST routes and a GraphQL endpoint. 

The data entries will collectively represent a company's organizational chart –– things like employees, teams, and offices. While a little bland, the concepts can be applied to absolutely anything else.

Optional: Trimming down the frontend

While an optional step, it makes a lot of sense to do this if your site won't be serving any content via pages but exclusively through APIs.

Once you have a hosted WordPress website, you can start by installing a minimalist WordPress theme like Blank Canvas and deleting every single demo post and page on your site. Continue by using the site editor to include information on the homepage for visitors who find it unintentionally. 

For example, add your business' name and logo, and tell them that they probably landed there in error. You can also include a button linking to the admin area for maintainers of the content. Something along the lines of:

One way to prevent your site from being found in search engine results is by checking the Discourage search engines from indexing this site in your site's settings.

If you would rather fully lock down the frontend and not even have the homepage described above, you can add the following code snippet either to a plugin like Code Snippets or to your child theme's functions.php file:

 /**  * Disables the frontend for non-logged-in users.  */ add_action( 	'template_redirect', 	static function (): void { 		$authorization_required_code = \WP_Http::UNAUTHORIZED; // 401  		if ( ! is_user_logged_in() ) { 			\status_header( $authorization_required_code ); 			die( \get_status_header_desc( $authorization_required_code ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 		} 	}  

Custom post types and taxonomies

Now it's time to focus on the website's admin area and the data modeling part of this tutorial. The most straightforward way of compartmentalizing your data is by using WordPress' built-in functionality of custom post types and custom taxonomies.

While there are many ways to do this, for the purposes of this tutorial, we'll organize our data like this:

  • An employee custom post type
  • A team custom taxonomy
  • An office custom post type

In order to create these custom data types, you can either add custom code to your site, or use a plugin (like in this video). A very popular plugin for creating custom post types and taxonomies using the admin interface is Custom Post Type UI – and that is what we'll be using in this tutorial.

Here is the JSON configuration for importing the data into your installation:

  • For custom post types 
{"employee":{"name":"employee","label":"Employees","singular_label":"Employee","description":"","public":"false","publicly_queryable":"false","show_ui":"true","show_in_nav_menus":"false","delete_with_user":"false","show_in_rest":"false","rest_base":"","rest_controller_class":"","rest_namespace":"","has_archive":"false","has_archive_string":"","exclude_from_search":"true","capability_type":"post","hierarchical":"false","can_export":"true","rewrite":"false","rewrite_slug":"","rewrite_withfront":"true","query_var":"false","query_var_slug":"","menu_position":"","show_in_menu":"true","show_in_menu_string":"","menu_icon":"dashicons-id","register_meta_box_cb":null,"supports":["title","thumbnail","excerpt","revisions"],"taxonomies":[],"labels":{"menu_name":"Employees","all_items":"All Employees","add_new":"Add new","add_new_item":"Add new Employee","edit_item":"Edit Employee","new_item":"New Employee","view_item":"View Employee","view_items":"View Employees","search_items":"Search Employees","not_found":"No Employees found","not_found_in_trash":"No Employees found in trash","parent":"Parent Employee:","featured_image":"Profile image for this Employee","set_featured_image":"Set profile image for this Employee","remove_featured_image":"Remove profile image for this Employee","use_featured_image":"Use as profile image for this Employee","archives":"Employee archives","insert_into_item":"Insert into Employee","uploaded_to_this_item":"Upload to this Employee","filter_items_list":"Filter Employees list","items_list_navigation":"Employees list navigation","items_list":"Employees list","attributes":"Employees attributes","name_admin_bar":"Employee","item_published":"Employee published","item_published_privately":"Employee published privately.","item_reverted_to_draft":"Employee reverted to draft.","item_trashed":"Employee trashed.","item_scheduled":"Employee scheduled","item_updated":"Employee updated.","parent_item_colon":"Parent Employee:"},"custom_supports":"","enter_title_here":"First and Last Names","show_in_graphql":"1","graphql_single_name":"Employee","graphql_plural_name":"Employees"},"office":{"name":"office","label":"Offices","singular_label":"Office","description":"","public":"false","publicly_queryable":"false","show_ui":"true","show_in_nav_menus":"false","delete_with_user":"false","show_in_rest":"false","rest_base":"","rest_controller_class":"","rest_namespace":"","has_archive":"false","has_archive_string":"","exclude_from_search":"true","capability_type":"post","hierarchical":"false","can_export":"false","rewrite":"false","rewrite_slug":"","rewrite_withfront":"true","query_var":"true","query_var_slug":"","menu_position":"","show_in_menu":"true","show_in_menu_string":"","menu_icon":"dashicons-admin-home","register_meta_box_cb":null,"supports":["title","thumbnail","revisions"],"taxonomies":[],"labels":{"menu_name":"Offices","all_items":"All Offices","add_new":"Add new","add_new_item":"Add new Office","edit_item":"Edit Office","new_item":"New Office","view_item":"View Office","view_items":"View Offices","search_items":"Search Offices","not_found":"No Offices found","not_found_in_trash":"No Offices found in trash","parent":"Parent Office:","featured_image":"Featured image for this Office","set_featured_image":"Set featured image for this Office","remove_featured_image":"Remove featured image for this Office","use_featured_image":"Use as featured image for this Office","archives":"Office archives","insert_into_item":"Insert into Office","uploaded_to_this_item":"Upload to this Office","filter_items_list":"Filter Offices list","items_list_navigation":"Offices list navigation","items_list":"Offices list","attributes":"Offices attributes","name_admin_bar":"Office","item_published":"Office published","item_published_privately":"Office published privately.","item_reverted_to_draft":"Office reverted to draft.","item_trashed":"Office trashed.","item_scheduled":"Office scheduled","item_updated":"Office updated.","parent_item_colon":"Parent Office:"},"custom_supports":"","enter_title_here":"Add Office","show_in_graphql":"1","graphql_single_name":"Office","graphql_plural_name":"Offices"}}
  • For custom taxonomies
{"team":{"name":"team","label":"Teams","singular_label":"Team","description":"","public":"false","publicly_queryable":"false","hierarchical":"false","show_ui":"true","show_in_menu":"true","show_in_nav_menus":"false","query_var":"false","query_var_slug":"","rewrite":"false","rewrite_slug":"","rewrite_withfront":"0","rewrite_hierarchical":"0","show_admin_column":"true","show_in_rest":"false","show_tagcloud":"false","sort":"false","show_in_quick_edit":"","rest_base":"","rest_controller_class":"","rest_namespace":"","labels":{"menu_name":"Teams","all_items":"All Teams","edit_item":"Edit Team","view_item":"View Team","update_item":"Update Team name","add_new_item":"Add new Team","new_item_name":"New Team name","parent_item":"Parent Team","parent_item_colon":"Parent Team:","search_items":"Search Teams","popular_items":"Popular Teams","separate_items_with_commas":"Separate Teams with commas","add_or_remove_items":"Add or remove Teams","choose_from_most_used":"Choose from the most used Teams","not_found":"No Teams found","no_terms":"No Teams","items_list_navigation":"Teams list navigation","items_list":"Teams list","back_to_items":"Back to Teams","name_field_description":"The name is how it appears on your site.","parent_field_description":"Assign a parent term to create a hierarchy. The term Jazz, for example, would be the parent of Bebop and Big Band.","slug_field_description":"The slug is the URL-friendly version of the name. It is usually all lowercase and contains only letters, numbers, and hyphens.","desc_field_description":"The description is not prominent by default; however, some themes may show it."},"meta_box_cb":"","default_term":"","object_types":["employee"],"show_in_graphql":"1","graphql_single_name":"Team","graphql_plural_name":"Teams"}}

At this point, your WordPress admin interface might look something like this:

To Gutenberg or not to Gutenberg

The Gutenberg block editor is functional, adaptable, and easy to use, and you should be using it to edit your traditional WordPress posts and pages. However, when it comes to CPTs without a frontend, there might not be any content to warrant the use of a performant editor like Gutenberg.

If you are positive that all of the information you need is not HTML-based, then it might make sense to disable Gutenberg for these CPTs and default back to the classic post editor that was the standard before WordPress 5.0.

The simplest way to disable Gutenberg support for a CPT is to set the show_in_rest argument to false when registering it (as we've done above). 

Alternatively, if you want to keep the built-in REST routes that WordPress provides for every CPT, you can add this code to your child theme:

 /**  * Disables the block editor for certain CPTs.  */ add_filter(     'use_block_editor_for_post_type',     static function( bool $use_block_editor, string $post_type ): bool { 	if ( in_array( $post_type, array( 'employee', 'office' ), true ) ) { 		$use_block_editor = false;        }    	return $use_block_editor;     },     10,     2 ); 

Custom Fields

Now that we have our basic data types in place, we need to start populating them with entries. Before we do that, we need to ensure that we can record all the necessary data on each entry, and for that we will need to build custom fields.

The easiest way to add custom fields to your custom post types is to register them with custom-fields support. When you then edit a post, it will include a metabox like this:

While this type of "key-value" interface can be enough, you might want to build a more user-friendly interface with fields like checkboxes, dropdowns, media selectors, and so on.

A popular way to add those types of custom fields is the Meta Box plugin, which, as mentioned above, is what we'll be using in this tutorial. Using their online custom fields generator, we got the PHP code needed to register the fields we wanted and then added them to Code Snippets.

Using a fake data generator, we populated the custom post types with a bit of seed data:

Other UI customizations

While we won't explore any further UI customization options in this tutorial, we wanted to note that it's possible to use various WordPress filters to tweak things like:

  • The default Add title placeholder on new posts (e.g., to First and Last Names)
  • The columns hidden or visible by default on the CPT list table view
  • Various other labels and messages throughout the admin interface

Access control

Before we start looking into making the data available via API, it's time to think about who should have access to it. 

The custom post types and taxonomies mentioned above were registered in such a way that any logged-in user with the ability to edit regular WordPress blog posts will also have the ability to edit these. However, it's possible to make that much more granular.

You can create custom user roles with custom capabilities in order to ensure that the UI is as clean-as-possible in order to promote focused-work for the users doing the data maintenance. This is particularly important if you anticipate a very high number of entries, especially on an ongoing basis. 

While it is possible to control this entirely with custom code, a way to maintain a simpler overview of access management is provided by Access Policies implemented by the Advanced Access Manager plugin. 

For example, you can create a separate access policy for each CPT you create. Then you may assign the policy either to a role or to individual users in order to maintain full control over who may add new Employee entries or even just edit existing ones. Deleting entries can be a capability reserved only for administrators.

Here is an example of how a policy named Employees CPT – Full Control and assigned only to Administrator users can look like:

 {     "Version": "1.0.0",     "Dependency": {         "wordpress": ">=6.6.2",         "advanced-access-manager": ">=6.9.42"     },     "Statement": [         {             "Effect": "allow",             "Resource": [                 "Capability:edit_employees",                 "Capability:edit_others_employees",                 "Capability:edit_private_employees",                 "Capability:edit_published_employees",                 "Capability:read_private_employees",                 "Capability:publish_employees",                 "Capability:delete_employees",                 "Capability:delete_private_employees",                 "Capability:delete_published_employees",                 "Capability:delete_others_employees"             ]         }     ] }  

Here is an example of what the admin interface can look like for a dummy operator user that has the Data Entry Operator user roles (cloned from the Subscriber role) with two AAM Access Policies attached – one for each custom CPT:

Notice how the lack of most menu items makes it easier to focus solely on the data-entry aspect. The policies can be made more granular, for example, to also restrict who may delete an entry or create new ones.

Custom REST routes

While WordPress will automatically create REST routes for every CPT as long as it is registered with the show_in_rest argument set to true, you can also create your own custom rest routes that are better suited for serving the CPT content in a way that makes more sense to your use-case.

The easiest and most standard way to achieve this is by extending one of the REST API controller classes. For maximum control over the output, you may want to extend the base WP_REST_Controller class itself.

You can choose to have your routes publicly accessible if the permission_callback argument is set to the __return_true function or you can choose to lock down calls using any permission scheme you want. 

The recommended way of locking down access is behind a capability check, i.e. a call to current_user_can. You can use the AAM Access Policies mentioned above to grant or withdraw permission from individual roles or users, and you can use WordPress' application passwords to authenticate API requests.

Hint: even if you decide that GET (read) requests should/can be publicly available, we still recommend that any POST // PUT // DELETE (create, update, delete) requests always be guarded by a current_user_can check.

Here is a REST controller that we added to Code Snippets in order to be able to list the employees on the site and fetch them by ID:

 add_action( 	'rest_api_init', 	function() { 		if ( ! class_exists( 'WP_REST_Controller' ) ) {  			return; 		} 	 		class Employees_Controller extends WP_REST_Controller { 			protected $namespace = 'custom/v1'; 			protected $rest_base = 'employees';  			public function register_routes(): void { 				register_rest_route( 					$this->namespace, 					"/$this->rest_base", 					array( 						array( 							'methods'             => WP_REST_Server::READABLE, 							'permission_callback' => array( $this, 'get_items_permissions_check' ), 							'callback'            => array( $this, 'get_items' ), 							'args'                => $this->get_collection_params(), 						), 						'schema' => array( $this, 'get_public_item_schema' ), 					) 				); 				 				register_rest_route( 					$this->namespace, 					"/$this->rest_base/(?P<employee_id>[\d]+)", 					array( 						'args'        => array( 							'employee_id' => array( 								'description' => __( 'Unique identifier for the employee.', 'psapi-features' ), 								'type'        => 'integer', 							), 						), 						array( 							'methods'             => WP_REST_Server::READABLE, 							'permission_callback' => array( $this, 'get_item_permissions_check' ), 							'callback'            => array( $this, 'get_item' ), 						), 						'schema'      => array( $this, 'get_public_item_schema' ), 					) 				); 			}  			public function get_items_permissions_check( $request ): WP_Error|bool { 				return true; // This information is public. You probably want to do a `current_user_can` check. 			} 			 			public function get_item_permissions_check( $request ): WP_Error|bool { 				return $this->get_items_permissions_check( $request ); // Same as for listing all. Can be different. 			}  			public function get_items( $request ): WP_Error|WP_REST_Response { 				$response = array();  				$employees = new WP_Query( $this->prepare_posts_query_args( $request ) ); 				foreach ( $employees->posts as $employee ) { 					$data       = $this->prepare_item_for_response( $employee, $request ); 					$response[] = $this->prepare_response_for_collection( $data ); 				}  				$response = rest_ensure_response( $response );  				$response->header( 'X-WP-Total', $employees->found_posts ); 				$response->header( 'X-WP-TotalPages', $employees->max_num_pages ); 				foreach ( $this->prepare_link_headers( $request, $employees->max_num_pages ) as $key => $value ) { 					$response->link_header( $key, $value ); 				}  				return $response; 			} 			 			public function get_item( $request ): WP_Error|WP_REST_Response { 				$employee = get_post( $request['employee_id'] ); 				if ( ! $employee ) { 					return new WP_Error( 'rest_not_found', __( 'No employee found for the given identifier.', 'wpcom-demo' ), array( 'status' => 404 ) ); 				} 				 				$response = $this->prepare_item_for_response( $employee, $request ); 				return rest_ensure_response( $response ); 			}  			public function prepare_item_for_response( $item, $request ): WP_Error|WP_REST_Response { 				$fields = $this->get_fields_for_response( $request ); 				$data   = array();  				if ( rest_is_field_included( 'id', $fields ) ) { 					$data['id'] = $item->ID; 				} 				if ( rest_is_field_included( 'name', $fields ) ) { 					$data['name'] = $item->post_title; 				} 				if ( rest_is_field_included( 'picture', $fields ) ) { 					$picture = get_the_post_thumbnail_url( $item, 'full' ); 					$data['picture'] = empty( $picture ) ? null : $picture; 				}  				$data     = rest_sanitize_value_from_schema( $data, $this->get_item_schema() ); 				$response = rest_ensure_response( $data ); 				if ( rest_is_field_included( '_links', $fields ) ) { 					$response->add_links( $this->prepare_links( $item, $request ) ); 				}  				return $response; 			}  			public function get_item_schema(): array { 				if ( $this->schema ) { 					return $this->add_additional_fields_schema( $this->schema ); 				}  				$this->schema = array( 					'$schema'    => 'http://json-schema.org/draft-04/schema#', 					'title'      => 'employee', 					'type'       => 'object', 					'properties' => array( 						'id'           => array( 							'description' => __( 'Unique identifier for the employee.', 'wpcom-demo' ), 							'type'        => 'integer', 							'readonly'    => true, 						), 						'name'         => array( 							'description' => __( 'The name of the employee.', 'wpcom-demo' ), 							'type'        => 'string', 							'required'    => true, 						), 						'picture'      => array( 							'description' => __( 'URL to the employee profile picture.', 'wpcom-demo' ), 							'type'        => array( 'string', 'null' ), 							'format'      => 'uri', 							'required'    => true, 						), 					) 				);  				return $this->add_additional_fields_schema( $this->schema ); 			} 			 			protected function prepare_posts_query_args( WP_REST_Request $request ): array { 				return array( 					'post_type'      => 'employee', 					'post_status'    => 'publish', 					'order'          => $request['order'], 					'orderby'        => $request['orderby'], 					'posts_per_page' => $request['per_page'], 					'paged'          => $request['page'], 					's'              => $request['search'] ?? '', 					'tax_query'      => $this->prepare_posts_taxonomy_query_args( $request ), // phpcs:ignore WordPress.DB.SlowDBQuery 				); 			} 			protected function prepare_posts_taxonomy_query_args( WP_REST_Request $request ): array { 				$tax_query = array();  				if ( $request['team'] ?? false ) { 					$tax_query[] = array( 						'taxonomy' => 'team', 						'field'    => 'slug', 						'terms'    => array( $request['team'] ), 					); 				}  				return $tax_query; 			} 			 			protected function prepare_link_headers( WP_REST_Request $request, int $max_pages ): array { 				$link_headers = array();  				$base = add_query_arg( 					urlencode_deep( $request->get_query_params() ), 					rest_url( $request->get_route() ) 				);  				$next_page = $request['page'] < $max_pages ? ( $request['page'] + 1 ) : null; 				if ( $next_page ) { 					$link_headers['next'] = add_query_arg( 'page', $next_page, $base ); 				}  				$prev_page = $request['page'] > 1 ? ( $request['page'] - 1 ) : null; 				if ( $prev_page ) { 					$link_headers['prev'] = add_query_arg( 'page', $prev_page, $base ); 				}  				return $link_headers; 			} 			 			protected function prepare_links( WP_Post $employee, WP_REST_Request $request ): array { 				$links = array(); 				 				if ( ! isset( $request['employee_id'] ) ) { 					$links['self'] = array( 						array( 							'href' => rest_url( "$this->namespace/$this->rest_base/{$employee->ID}" ), 						), 					); 				} else { 					$links['collection'] = array( 						array( 							'href' => rest_url( "$this->namespace/$this->rest_base" ), 						), 					); 				} 				 				return $links; 			} 		} 		 		( new Employees_Controller() )->register_routes(); 	} );  

Testing your REST routes

Your custom REST routes will be available under <your-domain>/wp-json/<route_namespace>/<route>. For example, the path for retrieving the list of employees could look like this:

 <your-domain>/wp-json/custom/v1/employees?team=marketing 

Hint: the team query added there will be parsed by WordPress and made available in the controller; you can then choose to either ignore it or filter the results by it – anything you want!

The easiest way to test your endpoints, especially if they will require an application password to access, is to use a tool like Postman which lets you test APIs in a very user-friendly manner. Publicly available GET requests can also be tested by simply visiting the URL endpoint in your browser!

Querying via GraphQL

Now that we are able to fetch the data via REST routes, let's explore how we might be able to fetch it using GraphQL as well.

If you're unfamiliar with GraphQL, what you need to know is that it's actually a querying language just like SQL but for APIs. You can read more about it on the official website over at https://graphql.org/. 

The simplest way to add GraphQL support to our site is by installing the newly-canonical plugin WPGraphQL. It also has a documentation page where you can learn more about what it provides out-of-the-box, and also examples of how to handle much more complex scenarios.  

If you've been paying attention to the JSON configuration of the custom post types shared above, you might've already noticed a key named show_in_graphql set to 1 (true/active). That is all we need in order to allow the custom post types we added to be queries using GraphQL.

Here is an example of a GraphQL query that can be used to list Employees which you can test in the built-in GraphiQL IDE bundled with the plugin:

 query GetEmployeesEdges {   employees {     edges {       node {         id         name: title         image: featuredImage {           node {             sourceUrl           }         }       }     }   } } 

Building your own

If this sounds like something you want to build for your own business, you can work on it on your own computer using Studio by WordPress.com. You can even share your work with colleagues (for free!) using a demo site, and when you're ready, any WordPress.com Business plan or higher will be able to host and manage your site.

Comment
Like
You can also reply to this email to leave a comment.

WordPress.com News © 2024.
Manage your email settings or unsubscribe.

WordPress.com and Jetpack Logos

Get the Jetpack app

Subscribe, bookmark, and get real‑time notifications - all from one app!

Download Jetpack on Google Play Download Jetpack from the App Store
WordPress.com Logo and Wordmark title=

Automattic, Inc.
60 29th St. #343, San Francisco, CA 94110

Tweet

0 Response to "Using WordPress as a Data-Entry Site to Power a Central API"

← Posting Lebih Baru Posting Lama → Beranda
Langganan: Posting Komentar (Atom)

Arsip Blog

  • Desember 2025 (17)
  • November 2025 (170)
  • Oktober 2025 (65)
  • September 2025 (69)
  • Agustus 2025 (69)
  • Juli 2025 (61)
  • Juni 2025 (74)
  • Mei 2025 (63)
  • April 2025 (77)
  • Maret 2025 (146)
  • Februari 2025 (132)
  • Januari 2025 (134)
  • Desember 2024 (177)
  • November 2024 (166)
  • Oktober 2024 (127)
  • September 2024 (141)
  • Agustus 2024 (127)
  • Juli 2024 (167)
  • Juni 2024 (132)
  • Mei 2024 (108)
  • April 2024 (102)
  • Maret 2024 (105)
  • Februari 2024 (85)
  • Januari 2024 (64)
  • Desember 2023 (74)
  • November 2023 (91)
  • Oktober 2023 (49)
  • September 2023 (47)
  • Agustus 2023 (63)
  • Juli 2023 (45)
  • Juni 2023 (47)
  • Mei 2023 (49)
  • April 2023 (42)
  • Maret 2023 (53)
  • Februari 2023 (38)
  • Januari 2023 (39)
  • Desember 2022 (47)
  • November 2022 (15)
  • Oktober 2022 (9)
  • September 2022 (10)
  • Agustus 2022 (9)
  • Juli 2022 (9)
  • Juni 2022 (8)
  • Mei 2022 (10)
  • April 2022 (13)
  • Maret 2022 (13)
  • Februari 2022 (16)
  • Januari 2022 (15)
  • Desember 2021 (18)
  • November 2021 (16)
  • Oktober 2021 (12)
  • September 2021 (10)
  • Agustus 2021 (12)
  • Juli 2021 (18)
  • Juni 2021 (11)
  • Mei 2021 (11)
  • April 2021 (13)
  • Maret 2021 (12)
  • Februari 2021 (12)
  • Januari 2021 (13)
  • Desember 2020 (10)
  • November 2020 (14)
  • Oktober 2020 (17)
  • September 2020 (10)
  • Agustus 2020 (14)
  • Juli 2020 (17)
  • Juni 2020 (21)
  • Mei 2020 (18)
  • April 2020 (11)
  • Maret 2020 (16)
  • Februari 2020 (8)
  • Januari 2020 (9)
  • Desember 2019 (13)
  • November 2019 (12)
  • Oktober 2019 (9)
  • September 2019 (11)
  • Agustus 2019 (26)
  • Juli 2019 (32)
  • Juni 2019 (32)
  • Mei 2019 (30)
  • April 2019 (33)
  • Maret 2019 (20)
  • Februari 2019 (12)
  • Januari 2019 (16)
  • Desember 2018 (14)
  • November 2018 (25)
  • Oktober 2018 (12)
  • September 2018 (14)
  • Agustus 2018 (14)
  • Juli 2018 (12)
  • Juni 2018 (27)
  • Mei 2018 (48)
  • April 2018 (33)
  • Maret 2018 (45)
  • Februari 2018 (73)
  • Januari 2018 (337)
  • Desember 2017 (338)
  • November 2017 (251)
  • Oktober 2017 (381)
  • September 2017 (31)

Label

  • Berita
  • Kajian Islam
  • Politik
Diberdayakan oleh Blogger.
  • How to Start a Successful Blog: A 12-Step Guide for Beginners
    So, you want to start a blog, huh? First of all, that's a pretty dope idea. ...
  • [New post] What’s New in the Block Editor: Edit Your Images, Drag and Drop Blocks and Patterns, and More
    Caroline Moore posted: " From time-saving features to enhancements in your favorite blocks, these block editor improvements...
  • Hot Off the Press: New WordPress.com Themes for January 2025
    New Year, new themes! We've been hard at work developing new themes to help ...
↑
Copyright © 2017 - Muslim Online - All rights reserved Powered by Blogger