I am making some progress with creating a custom plugin, and am curious as to whether I am following proper "Craft standards". Here is my scenario:

A user needs to submit a one field form that contains their email address. Upon button click, I would like to prevent the default action, and send the email address via AJAX to my plugin for processing.

After reading through the Craft Plugin Docs, I am under the impression that my plugin requires the following functionality(and in this order).

  1. Send email address via AJAX to a Controller.
  2. The controller will then extract the email, and send it off to a Service.
  3. The Service, is where I should write my code that connects to the MailChimp API
  4. Send the result of whether the Mailchimp API request was successful from the Service, back to the Controller.
  5. The Controller will then send back the response to my JavaScript, where I will ultimately display a success or error message to the user.

I have been successful in sending an AJAX request from my JavaScript code to my Controller via the following code, and receiving some dumby test text.

function sendAjax() {
    var xhr = new XMLHttpRequest()
    xhr.open('POST', siteUrl + 'ajax/ajax-mailchimp.html')
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8')

    xhr.onload = function() {
      if(xhr.status === 200) {
    xhr.send(encodeURI('action=/craftMailchimped/submittal/receiveEmail&[email protected]'))

In my controller, I have the following:

namespace Craft;

class CraftMailchimped_SubmittalController extends BaseController {

    public function actionReceiveEmail() {
        echo 'Wuzup?';

In case you are wondering what 'ajax/ajax-mailchimp.html' is, its an empty template file, with no code in it. I did this, because I didn't really know what to put in as the URL in the xhr.POST method in my JavaScript code.

I am concerned I am not going about this in the right way, as I wouldn't even know how I would extract the email address inside my controller.

Any help on this?

My Form HTML for reference:

<form id="updates" class="mchimp">
    <p class="newsletter">
      {{ entry.connectNewsletterCta }}
    <div class="elements-wrapper">
      <input type="email" placeholder="Enter your email" required>
      <button class="btn">
        {{ entry.connectNewsletterButtonText }}

1 Answer 1


It's actually quite simple. First you need a form:

<form method="post" id="myForm">
    <!-- insert your route as an action parameter in camelCase -->
    <!-- pluginName/controllerName/methodName -->
    <input type="hidden" name="action" value="craftMailchimped/submittal/receiveEmail">
    <input type="text" name="email">
    <input type="button" id="submit" value="Submit">

Then you have your javascript (I'll use jQuery ajax for that)

        type: "post",
        url: '', //<- leave the url blank.. always...
        data: $("#myForm").serialize(),
        success: function(data){
            if(data.success === true){
                //display your data.errors here
        error: function (XMLHttpRequest, textStatus) {
            console.log("Status: " + textStatus);

And your controller and this model

public function actionReceiveEmail() {
    $response = [
        'success'   => false,
        'message'   => Craft::t('something went wrong')

    $emailModel = new CraftMailchimped_EmailModel();
    $emailModel->email = craft()->request->getParam('email');

        $response['success'] = true;
        $response['message'] = Craft::t('wuhu great :D');
        $response['errors'] = $emailModel->getErrors();


Your service could contain this method

public function subscribe(CraftMailchimped_EmailModel $emailModel){
    if($success = $emailModel->validate()){
        //make your API calls
        $response = ......;
        if($response['error'] === 444){
            $emailModel->addError('email', Craft::t('Email is already taken'));
            $success = false;

    return $success;

So you are totally right with your points. In case you want to see how to manage records in your database take a look at this plugin Click I created it for learning purposes for someone else in this thread. Maybe you'll find it useful to learn some stuff like fetching objects from the database and inserting.

to resume

  1. You should always leave the url blank, define an action parameter for your route. The request type does not matter, it's the same for ajax and normal requests
  2. Your Route is always pluginHandle/controllerName/functionName in camelCase
  3. Your messages and responses should always be defined in your Controllers, your Services are just for logic that return true or false (or some objects like craft()->entries->getEntryById()), you can create models with validations for error messages you can use them like that: (validators will provide the translated messages)

    // in your controller
        $response['success'] = true;
        $response['errors'] = $mailChimpModel->getErrors();

    $response['errors'] is an array with all field handles and the corresponding translated error messages like 'email' => ['This Mail is already in the list']

  4. Crafts RequestService has all the methods you need to access the request variables. Those values are all safely escaped and secure, so you can be sure there won't be security measures. You can access parameters with craft()->request->getParam('key', default value);
  • Back to work tomorrow. I can give this a read over then, when I'm actually looking at my real code. Thanks again, and will vote on this asap. Dec 18, 2017 at 1:19
  • 1
    craftcms.com/docs/plugins/… Dec 18, 2017 at 14:49
  • Thanks man. I used something close to this. Excellent, and in-depth answer as usual. Dec 18, 2017 at 15:22
  • Does it work for you now? If not we can continue our conversation in a chat if you like. Btw don't worry once you get used to it, it is much faster than regular PHP Dec 18, 2017 at 15:24
  • No idea, how to message people on here. Dec 18, 2017 at 15:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.